线程间通信的方法
在Java中,我们可以使用
wait()
wait(long timeout)
wait(long timeout,int naos)
notify()
notifyAll()
这5个方法来实现线程间的通信。
其中wait()方法的作用是,阻塞当前线程(阻塞的原因常常是一些必要的条件还没有满足),一个线程在调用wait()方法后,会释放当前的锁对象,不再参与竞争,直到其他线程调用了这个锁的notifyAll()、或notify()方法,这个被阻塞的线程才会有可能继续执行。
注意,上面说的是有可能继续执行,这是为什么呢?这是因为,当那些被notifyAll()唤醒的线程,同时去抢一把锁时,只有一个线程可以成功抢到,此时成功抢到锁的线程才能继续执行,而那些没有抢到锁的线程,则继续是阻塞状态。
notify()、notifyAll()方法的作用是,唤醒那些调用了wait()方法的线程。notify()是唤醒其中一个,notifyAll()是全部唤醒。
上述的5个方法,都是定义在Object类中。
锁池与等待池
锁池:假设线程A已经拥有了某个对象的monitor锁,而其他的线程调用这个对象的synchronized方法(或者synchronized代码块),由于这些线程在进入已经被对象的synchronized方法之前必须先获得该对象的monitor的拥有权,但是该对象的锁目前正在被线程A拥有,所以这些想要获取该对象monitor拥有权的线程就进入到了该对象的锁池中。
等待池: 假设一个线程A调用了某个对象的wait()方法,线程A就会释放该对象的monitor,进入到该对象的等待池中。
我们通过一个很简单的伪代码再来看一看锁池:
class Handler extends Thread{
Object o = new Object
public void run(){
sychronized(o){
...
}
}
}
假设现在有两个线程,分别为A、B。假设线程A先执行,那么线程A会执行到synchronized代码块时,首先获取对象o的monitor,此时,对象o的monitor的拥有者为线程A,然后线程A进入synchronized代码块执行。此时,线程B也要执行synchronized代码块里的内容,它发现对象o的monitor已经被占用了,此时,线程B就会进入到对象o的锁池中等待线程A释放掉对象o的monitor的使用权。
notify()、notifyAll()区别
notify()、notifyAll()的区别在于:
notify唤醒的是对象等待池中的一个线程,这个线程进入Blocked状态,开始抢锁(注意,被唤醒的线程进入BLOCKED状态,而不是RUNNABLE状态),当这个线程执行完释放锁的时候,即使没有其他线程占用锁,其它处于wait状态的等待池中的线程也会继续等待被唤醒。即处于对象等待池的线程,不会主动去抢锁。
调用notifyAll()后,所有处于对象等待池中的线程都会被唤醒,当这个对象的锁被释放掉后,这些原处于等待池中的线程都会去抢锁。抢到锁的线程去执行,没有抢到锁的线程进入BLOCKED状态。
三个线程轮流打印ABC
我们通过一个例子,去体会线程间的通信。我们使用三个线程,使这三个线程轮流打印A、B、C共10次。
class ABCHandler{
private Object o = new Object();
private