不是太细的java自学笔记进阶篇1(p389-p447)(多线程,线程的四种实现方法,同步代码块,同步方法,线程通信)

tech2026-02-21  2

笔记同样基于此教学视频,我觉得讲的很好了内容也很全面,适合自学:

教程是这个

目录:

1.多线程基础

第一种方法创建线程:通过java.lang.Thread包创建线程。

第二种方法创建线程:

2.解决线程安全问题

方式1:同步代码块(synchronized)

方式2:同步方法

方法3:Lock锁

3.线程通信

4.java5.0新增的线程创建方式

1.实现Callable接口

2.使用线程池


1.多线程基础

 

方法区和堆是一个进程一份

栈和程序计数器是一个线程一份

 

第一种方法创建线程:通过java.lang.Thread包创建线程。

  

如果直接调用t1.run()不会开启新线程,必须要调用start方法。每个线程只能启动。如果多个线程,则写多个class,重写多个run方法。

要是只用一次可以直接写匿名子类

  

yield()释放当前cpu的执行权。可能下次分配资源又到本资源。

join()在线程a中调用线程b的join方法,此时a阻塞,直到b执行完,线程a才继续执行。

sleep(long milliontime)指定休眠需要时间。

isAlive()是否存活

  

从概率上讲,高优先级的线程先执行的概率更高。

 

第二种方法创建线程:

   

使用start方法,调用了当前线程的run方法,其实start调用了runnable类型的target的run()

 

上述两种方式的比较:

第一种方式中本身有可能需要继承其他父类,如果继承了thread类会导致没法继承其他类(类的单继承局限性)。第一种方式没法进行数据共享。需要额外加static。

开发中优先选择实现Runnable接口的方式。

在底层中Thread是Runnable接口的实现。最终都是重写run方法。

 

2.解决线程安全问题

方式1:同步代码块(synchronized)

synchronized(同步监视器俗称锁){ //需要被同步的代码 (操作共享数据的代码) }

任何一个类的对象都可以充当锁,按时多个线程要共用一个锁。(需要注意锁的位置,要保证唯一锁)

继承thread时不可以把锁写成this(慎用)。实现Runnable方式时可以使用this作为锁(原理为第二种创建多线程方法的特性),同样可以使用类来充当锁

代码块不要包多了,很可能导致某个线程从头到尾,也不能包少了。

方式2:同步方法

如果某个方法中全部需要解决线程安全则可以把这个方法声明为一个synchronized方法

没有显示的声明同步监视器,使用this作为监视器。

此方法在继承Thread中需要把方法声明为static方法,这样可以调用我们继承的类作为锁。

 

方法3:Lock锁

   

本方法和synchronized方法的异同:

相同:都是解决线程安全问题

不同:synchronized自动的释放同步监视器

   lock需要手动的启动同步

3.线程通信

线程通信的三个方法

wait():阻塞线程,释放同步监视器

notify():唤醒一个线程,多个线程阻塞则唤醒优先级高的

notifyAll():唤醒所有线程

注意:

1.只能在同步方法和同步代码块中使用,否则无法获得锁。

2.上述三个方法必须调用的是同步方法或者是同步代码块中的监视器

3.上述三个方法是定义在object类中。

sleep()和wait()的异同?

相同:都使线程进入阻塞状态

不同:声明位置不同,sleep在Thread类中,wait在Object类中

调用要求不同,sleep任何位置都可以,wait只能在同步方法或者是同步代码块中

sleep不会释放锁,wait会

这里举一个消费者生产者的例子用于学习:

结果:

代码:

package com.lzy.javatest; /** * @author: lzy * @description: 生产者消费者问题商店最多:0 * @date: 2020-09-04-9:59 */ class Clerk{//店员 private int productCount=0; public synchronized void produceProduct() { if(productCount<20){ productCount++; System.out.println(Thread.currentThread().getName()+":开始生产产品:"+productCount); //只要生产了一个产品就可以开始消费了 notify(); } else{ try { wait(); } catch (InterruptedException e) { e.printStackTrace(); } } } public synchronized void consumeProduct() { if (productCount>0) { System.out.println(Thread.currentThread().getName()+":开始消费产品:"+ productCount); productCount--; //只要消费了一个产品就可以开始生产了,解除阻塞 notify(); }else{ try { wait(); } catch (InterruptedException e) { e.printStackTrace(); } } } } class Producer extends Thread{//生产者 private Clerk clerk; public Producer(Clerk clerk){ this.clerk=clerk; } @Override public void run() { System.out.println(getName()+"开始生产产品"); while (true){ clerk.produceProduct(); } } } class Consumer extends Thread{//消费者 private Clerk clerk; public Consumer(Clerk clerk){ this.clerk=clerk; } @Override public void run() { System.out.println(getName()+"开始消费产品"); while (true){ clerk.consumeProduct(); } } } public class ProductTest { public static void main(String[] args) { Clerk clerk=new Clerk(); Producer p1=new Producer(clerk); p1.setName("生产者1:"); Consumer c1=new Consumer(clerk); c1.setName("消费者1:"); p1.start(); c1.start(); } }

4.java5.0新增的线程创建方式

1.实现Callable接口

相比Runnable更加强大

可以有返回值,可以抛出异常,支持泛型的返回值,需要借助FutureTask类

2.Future接口

   

案例代码:

package com.lzy.javatest; import java.util.concurrent.Callable; import java.util.concurrent.ExecutionException; import java.util.concurrent.FutureTask; /** * @author: lzy * @description: 第三种方式创建线程 ,实现Calable接口 * @date: 2020-09-04-15:34 */ //创建一个callable实现类 class NumThread implements Callable{ //实现call方法 @Override public Object call() throws Exception { int sum=0; for (int i=0;i<100;i++){ if(i%2==0){ System.out.println(i); sum+=i; } } return sum; } } public class Thread3th { public static void main(String[] args) { //创建实现类对象 NumThread numThread=new NumThread(); //实现类的对象床底到FutureTask类中作为参数,创建FutureTask对象 FutureTask futureTask=new FutureTask(numThread); //缺少这一句就无法启动线程 new Thread(futureTask).start(); //输出结果 try { Object sum=futureTask.get(); System.out.println(sum); } catch (InterruptedException e) { e.printStackTrace(); } catch (ExecutionException e) { e.printStackTrace(); } } }

2.使用线程池

提前创建多个线程,放入线程池中,使用时直接获取,使用完放回线程池中,可以避免频繁创建销毁实现重复利用。(类似私家车和公交车)

好处:提高响应速度,降低资源消耗,便于线程管理

  

package com.lzy.javatest; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; import java.util.concurrent.ThreadPoolExecutor; /** * @author: lzy * @description:创建线程第四种方法-线程池 * @date: 2020-09-04-15:56 */ class NumberThread implements Runnable{ @Override public void run() { System.out.println("启动NumberThread线程"); } } public class Thread4th { public static void main(String[] args) { //制造一个线程池 ExecutorService service= Executors.newFixedThreadPool(10); //设置线程池的属性 //注意这里需要强转为ThreadPoolExecutor类型,因为ExecutorService是一个接口 ThreadPoolExecutor service1=(ThreadPoolExecutor)service; service1.setCorePoolSize(15); //创建一个实现类和其对象,根据实现不同使用不同的方法调用线程 service.execute(new NumberThread());//适合Runnable //service.submit();//适合Callable //关闭连接处 service.shutdown(); } }

需要注意的是,这个地方设置线程池的属性,需要把类型强转成其实现类的类型。接口并没有实现控制线程池的方法。

根据源码我们可以看到:

ThreadPoolExecutor是AbstractExecutorService的子类,AbstractExecutorService又是ExecutorService接口的实现类。

 

 

 

 

 

 

 

最新回复(0)