线程等待通知机制引入
在线程中,可以使用Thread.join() 的方式,让线程进入等待状态。而join方法所带来的结果是等待其他线程的结束,等待通知机制的引入是为了让线程不一定要等待到结束以后才继续执行,是更精细的线程控制技巧。
等待通知机制的作用
等待通知机制可以安排线程之间的执行顺序,更巧妙的进行规划控制。可以有效的解决例如“线程饿死”等状况的出现。
线程饿死:由于线程是抢占式执行,当一个线程频繁获取和释放锁而造成cpu资源无法分配。
等待通知机制的使用
在等待通知机制中存在两个方法:wait和notify。通过条件判定的方法查看当前逻辑能否执行,如果不能执行就使用wait方法,线程主动进入阻塞等待状态,将执行机会让给别的锁。
这样的机制避免了线程进行一些无意义的重试。
等待后续条件满足,其他线程使用notify方法通知阻塞线程,重新唤醒该线程。
案例解析
在下面的代码案例中,线程t1进入等待状态,线程t2通知线程t1,唤醒t1之后执行后续代码。
过程:在获取锁locker以后,wait方法**主动进入阻塞状态,主动释放锁locker,**之后线程t2获取到锁locker,开始执行后续代码。
在t2调用notify方法以后,线程t1被唤醒,继续执行后续代码。
public static void main(String[] args) throws InterruptedException {
Object locker = new Object();
Thread t1 = new Thread(()-> {
synchronized (locker) {
System.out.println("t1等待之前");
try {
locker.wait();
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
System.out.println("t1等待之后");
}
});
Thread t2 = new Thread(()-> {
synchronized (locker) {
System.out.println("t2通知之前");
Scanner scanner = new Scanner(System.in);
System.out.println("输入数字");
int x = scanner.nextInt();
locker.notify();
System.out.println("t2通知之后");
}
});
t1.start();
Thread.sleep(500);
t2.start();
}
wait和notify的进阶使用
wait和notify两种方法都是Object自带的方法,只要是一个对象都可以进行调用。
在wait方法中,线程主动进行阻塞等待,同时也可以设置等待时间,只要超过等待时间,线程就会自动唤醒。
在notify方法中,存在多个线程时,通知只能随机唤醒其中的一个。如果想要全部唤醒,也可以使用notifyAll()方法。
//线程t1等待
try {
locker.wait(1000*10);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
//线程t2通知
locker.notifyAll();
wait和sleep的区别
- wait是Object类中的一个方法,sleep是Thread类中的一个方法;
- wait必须在synchronized修饰的代码块或方法中使用,sleep可以在任何位置使用
- wait被调用以后线程进入BLOCK状态并主动释放锁,同时可以通过notify进行唤醒;sleep被调用以后进入TIMED_WAIT状态,不涉及锁的相关操作
总结
等待通知机制在一定程度上解决了资源浪费的情况,同时也使得多线程具有了灵活性。同时我们需要注意,线程启动顺序。在案例中,使用sleep(500)是为了保证线程t1和t2之间抢占式执行,使得t2优先执行,这样会导致后续线程t1只能进入死等状态。
源码☞等待通知机制