实现主线程等待子线程的几种方案
- 通过AQS下的CountDownLatch来实现。
- 通过Thread.join()方法
- 通过LockSupport.park()以及LockSupport.unpark()配合实现
- 通过Object.wait()配合Object.notify()/Obejct.notifyAll()配合实现
基本知道这几种方式,就够了。
不同方式的关系
其中方式1和方式3都是基于AQS;AQS的各个子类实现,比如可重入锁,可重入读写锁、CountDownLatch等,都是通过LockSuppoort.park()实现线程阻塞,然后把线程加入到阻塞队列中去。然后再通过LockSupport.unpark()实现线程的唤醒;
而方式2其实底层实现是基于方式4的,我们看源码就可以看的出来;
/**
* Waits at most {@code millis} milliseconds for this thread to
* die. A timeout of {@code 0} means to wait forever.
*
* <p> This implementation uses a loop of {@code this.wait} calls
* conditioned on {@code this.isAlive}. As a thread terminates the
* {@code this.notifyAll} method is invoked. It is recommended that
* applications not use wait, notify, or
* notifyAll on Thread instances.
*/
public final synchronized void join(long millis)
throws InterruptedException {
long base = System.currentTimeMillis();
long now = 0;
if (millis < 0) {
throw new IllegalArgumentException("timeout value is negative");
}
if (millis == 0) {
while (isAlive()) {
//这里调用了wait方法
wait(0);
}
} else {
while (isAlive()) {
long delay = millis - now;
if (delay <= 0) {
break;
}
//这里调用了wait方法啊
wait(delay);
now = System.currentTimeMillis() - base;
}
}
}
使用wait和notify的简单实现
下面是一个简单的示例:
我们创建一个了子线程son,子线程会将传入其中的线程唤醒;
而主线程main中,我们先启动子线程son,并将main作为参数传入其中,最后再阻塞自己;
这样,就可以依靠子线程son,唤醒主线程main。
public class WaitNotifyTest extends Thread{
//私有参数1:传入的线程,将会被唤醒
private Thread mainThread;
//同步监视器,其实相当于一个锁
private Object object;
//构造方法
WaitNotifyTest(Thread mainThread, Object o){
this.mainThread = mainThread;
this.object = o;
}
@Override
public void run(){
//子线程获取同步监视器的锁;该操作基于主线程释放锁,否则子线程无法获取锁
synchronized(object){
System.out.println("子线程开始执行");
try {
//子线程等待一会,让主线程阻塞的操作生效
Thread.sleep(4000);
}catch (InterruptedException e){
e.printStackTrace();
}
//唤醒被同步监视器阻塞住的所有线程
object.notifyAll();
System.out.println("主线程被唤醒");
}
}
public static void main(String[] args) {
//创建一个同步监视器
Object lock = new Object();
//将同步监视器和主线程传入子线程中
WaitNotifyTest waitNotifyTest = new WaitNotifyTest(Thread.currentThread(),lock);
//主线程先获取锁,
synchronized (lock){
System.out.println("主线程被阻塞");
//主线程启动子线程
waitNotifyTest.start();
System.out.println("启动子线程");
try {
//主线程阻塞,并且wait()操作会使当前线程释放获取的锁
//从而子线程可以获取锁,执行同步方法
lock.wait();
}catch (InterruptedException e){
e.printStackTrace();
}
}
System.out.println("主线程执行完成");
}
}
wait和park的异别
- wait和park都会将线程阻塞,但是wait方法会释放锁,park方法则不会;
- wait方法阻塞的线程和park方法阻塞线程都可以响应中断,但wait阻塞线程会抛出InterruptedException,而park方法则不会抛出异常。
- wait和notify/notifyAll需要保证先后顺序,比如示例中,先调用的是wait()再调用的是notify,这样主线程才能被正常唤醒;如果先调用notify,再调用wait,主线程会一直阻塞。而park和unpark则不需要保证先后顺序。原因在于,park实现的是一个将许可证+1,unpark是将许可证-1,只要保证最终是0,线程即可正常运行。