05-线程间的通信
线程之间协调工作的方式
基于等待通知模型的通信
等待/通知的相关方法是任意Java对象都具备的,因为这些方法被定义在所有对象的超类java.lang.Object上。
相关API
notify: 通知一个对象上等待的线程,使其从wait方法返回,而返回的前提是该线程获取到了对象的锁
notifyAll: 通知对象上所有等待的线程,使其从wait方法返回
wait: 使线程进入WAITING(后面线程的生命周期讲)状态,只有等待另一个线程通知或者被中断才返回,需要注意的是,调用wait方法后需要释放对象的锁
wait(long): 和wait类似,加入了超时时间,超时了还没被通知就直接返回
wait(long, int): 纳秒级,不常用
一些需要注意的点:
使用wait()、notify()和notifyAll()时需要先对调用对象加锁。
调用wait()方法后,线程状态由RUNNING变为WAITING,并将当前线程放置到对象的等待队列,释放锁。
notify()或notifyAll()方法调用后,等待线程不会立即从wait()返回,需要调用notify()或notifAll()的线程释放锁之后,等待线程才有机会从wait()返回。
notify()方法将等待队列中的一个等待线程从等待队列中移到同步队列中,而notifyAll()方法则是将等待队列中所有的线程全部移到同步队列,被移动的线程状态由WAITING变为BLOCKED。
从wait()方法返回的前提是获得了调用对象的锁。
关于等待队列和同步队列
同步队列(锁池):假设线程A已经拥有了某个对象(注意:不是类)的锁,而其它的线程想要调用这个对象的某个synchronized方法(或者synchronized块),由于这些线程在进入对象的synchronized方法之前必须先获得该对象的锁的拥有权,但是该对象的锁目前正被线程A拥有,所以这些线程就进入了该对象的同步队列(锁池)中,这些线程状态为Blocked。
等待队列(等待池):假设一个线程A调用了某个对象的wait()方法,线程A就会释放该对象的锁(因为wait()方法必须出现在synchronized中,这样自然在执行wait()方法之前线程A就已经拥有了该对象的锁),同时 线程A就进入到了该对象的等待队列(等待池)中,此时线程A状态为Waiting。如果另外的一个线程调用了相同对象的notifyAll()方法,那么 处于该对象的等待池中的线程就会全部进入该对象的同步队列(锁池)中,准备争夺锁的拥有权。如果另外的一个线程调用了相同对象的notify()方法,那么 仅仅有一个处于该对象的等待池中的线程(随机)会进入该对象的同步队列(锁池)。
等待通知模型的示例
模型
等待者
通知者
基于Condition的通信
上述的这种等待通知需要使用synchronized, 如果使用Lock的话就要用Condition
了
Condition接口也提供了类似Object的监视器方法,与Lock配合可以实现等待/通知模式
Condition与Object监视器的区别
项目 | Object的监视器方法 | Condition |
前置条件 | 获得对象的锁 | Lock.lock()获取锁 Lock.newCondition()获取Condition |
调用方式 | obj.wait() | condition.await() |
等待队列个数 | 一个 | 可以多个 |
当前线程释放锁并进入等待状态 | 支持 | 支持 |
等待状态中不响应中断 | 不支持 | 支持 |
释放锁进入超时等待状态 | 支持 | 支持 |
进入等待状态到将来的某个时间 | 不支持 | 支持 |
唤醒等待中的一个或多个线程 | 支持 notify notifyAll | 支持signal signalAll |
这里有一些线程的状态,可以看完后边的线程的生命周期再回过头看看
示例
一般都会将Condition对象作为成员变量。当调用await()方法后,当前线程会释放锁并在此等待,而其他线程调用Condition对象的signal()方法,通知当前线程后,当前线程才从await()方法返回,并且在返回前已经获取了锁。
实现一个有界队列,当队列为空时阻塞消费线程,当队列满时阻塞生产线程
基于BlockingQueue实现线程通信
这里列个标题,后边说到了BlockingQueue就明白怎么做了。
可以参看第05章的相关内容
最后更新于