线程与线程之间不是相互独立的存在,它们彼此之间需要相互通信和协作。最典型的例子就是生产者-消费者问题。下面首先介绍 wait/notify 机制,并对实现该机制的两种方式:synchronized+wait-notify模式和 Lock+Condition 模式进行介绍,作为线程间通信与协作的基础。最后,对 Thread 类中的 join() 方法进行源码分析,并以宿主线程与寄生线程的协作为例进行说明。
二、使用 wait/notify/notifyAll 实现线程间通信的几点重要说明1、Object是所有类的父类,它有5个方法组成了等待/通知机制的核心:notify()、notifyAll()、wait()、wait(long)和wait(long,int)。在Java中,所有的类都从Object继承而来,因此,所有的类都拥有这些共有方法可供使用。而且,由于他们都被声明为final,因此在子类中不能覆写任何一个方法。
2、在Java中,可以通过配合调用Object对象的 wait() 方法和 notify() 方法或 notifyAll() 方法来实现线程间的通信。上述三个方法均非Thread类中所声明的方法,而是Object类中声明的方法。原因是每个对象都拥有monitor(锁),所以让当前线程等待某个对象的锁,当然应该通过这个对象来操作,而不是用当前线程来操作,因为当前线程可能会等待多个线程的锁,如果通过线程来操作,就非常复杂了。
3、在线程中调用 wait() 方法,将阻塞等待其他线程的通知(其他线程调用 notify() 方法或 notifyAll() 方法),在线程中调用 notify() 方法或 notifyAll() 方法,将通知其他线程从 wait() 方法处返回。
4、wait()、notify()、notifyAll() 使用条件: 必须放在synchronized代码块中;
必须要锁住同一个对象,notify()方法才能唤醒等待的线程;
notify()只会随机的唤醒一个处于wait状态的线程;
notifyall()会全部唤醒所有处于wait线程,争夺到时间片的只有一个;
5、wait()方法
该方法用来将当前线程置入休眠状态,直到接到通知或被中断为止。在调用 wait() 之前,线程必须要获得该对象的对象级别锁,即只能在同步方法或同步块中调用 wait() 方法。 进入 wait() 方法后,当前线程释放锁。在从 wait() 返回前,线程与其他线程竞争重新获得锁。如果调用 wait() 时,没有持有适当的锁,则抛出IllegalMonitorStateException,它是RuntimeException的一个子类。
6、notify()该方法也要在同步方法或同步块中调用,即在调用前,线程也必须要获得该对象的对象级别锁,如果调用 notify() 时没有持有适当的锁,也会抛出IllegalMonitorStateException。
该方法用来通知那些可能等待该对象的对象锁的其他线程。如果有多个线程等待,则线程规划器会随机挑选出其中一个 wait() 状态的线程来发出通知,并使它等待获取该对象的对象锁(notify后,当前线程不会马上释放该对象锁,wait所在的线程并不能马上获取该对象锁,要等到程序退出synchronized代码块后,当前线程才会释放锁,wait所在的线程也才可以获取该对象锁),但不惊动其他同样在等待被该对象notify的线程们。当第一个获得了该对象锁的 wait 线程运行完毕以后,它会释放掉该对象锁,此时如果该对象没有再次使用notify语句,则即便该对象已经空闲,其他wait状态等待的线程由于没有得到该对象的通知,会继续阻塞在wait状态,直到这个对象发出一个notify或notifyAll。这里需要注意:它们等待的是被notify或notifyAll,而不是锁。这与下面的notifyAll()方法执行后的情况不同。
7、notifyAll()该方法与 notify() 方法的工作方式相同,重要的一点差异是:
notifyAll() 使所有原来在该对象上 wait 的线程统统退出 wait 的状态(即全部被唤醒,不再等待notify或notifyAll,但由于此时还没有获取到该对象锁,因此还不能继续往下执行),变成等待获取该对象上的锁, 一旦该对象锁被释放(notifyAll线程退出调用了notifyAll的synchronized代码块的时候),它们就会去竞争。如果其中一个线程获得了该对象锁,它就会继续往下执行,在它退出synchronized代码块,释放锁后,其他的已经被唤醒的线程将会继续竞争获取该锁,一直进行下去,直到所有被唤醒的线程都执行完毕。
8、wait(long) 和 wait(long,int)这两个方法是设置等待超时时间的,后者在超值时间上加上ns,精度也难以达到,因此,该方法很少使用。对于前者,如果在等待的线程接到通知或被中断之前,已经超过了指定的毫秒数,则它通过竞争重新获得锁,并从 wait(long) 返回。另外,需要知道,如果设置了超时时间,当 wait() 返回时,我们不能确定它是因为接到了通知还是因为超时而返回的,因为 wait() 方法不会返回任何相关的信息。但一般可以通过设置标志位来判断,在notify之前改变标志位的值,在 wait() 方法后读取该标志位的值来判断,当然为了保证notify不被遗漏,我们还需要另外一个标志位来循环判断是否调用 wait() 方法。
9、小结a. wait()、notify() 和 notifyAll()方法是 本地方法,并且为 final 方法,无法被重写;
b. 调用某个对象的 wait() 方法能让 当前线程阻塞,并且当前线程必须拥有此对象的monitor(即锁);
c. 调用某个对象的 notify() 方法能够唤醒 一个正在等待这个对象的monitor的线程,如果有多个线程都
在等待这个对象的monitor,则只能唤醒其中一个线程;
d. 调用notifyAll()方法能够唤醒所有正在等待这个对象的monitor的线程。
三、生产者-消费者模型
线程与线程之间相互通信和协作,最经典的例子就是 生产者-消费者模型: 在下面的例子中,虽然两个线程实现了通信,但是凭借 线程B 不断地通过 while语句轮询 来检测某一个条件,这样会导致CPU的浪费。因此,需要一种机制来减少 CPU资源 的浪费,而且还能实现多个线程之间的通信,即 wait/notify 机制 。
//资源类
class MyList {
//临界资源
private volatile List list = new ArrayList();
public void add() {
list.add("abc");
}
public int size() {
return list.size();
}
}
// 线程A
class ThreadA extends Thread {
private MyList list;//临界资源
public ThreadA(MyList list,String name) {
super(name);
this.list = list;
}
@Override
public void run() {
try {
for (int i = 0; i
关注
打赏
最近更新
- 深拷贝和浅拷贝的区别(重点)
- 【Vue】走进Vue框架世界
- 【云服务器】项目部署—搭建网站—vue电商后台管理系统
- 【React介绍】 一文带你深入React
- 【React】React组件实例的三大属性之state,props,refs(你学废了吗)
- 【脚手架VueCLI】从零开始,创建一个VUE项目
- 【React】深入理解React组件生命周期----图文详解(含代码)
- 【React】DOM的Diffing算法是什么?以及DOM中key的作用----经典面试题
- 【React】1_使用React脚手架创建项目步骤--------详解(含项目结构说明)
- 【React】2_如何使用react脚手架写一个简单的页面?