我们知道多线程执行的时候是抢占式执行的,这样虽然大大提高了效率,但是也会导致许多问题出现,可以说是有利有弊。“线程饿死”就是其中一个弊端。
当我们在多个线程中加入锁之后,由于这些线程是抢占式并发执行的,这些线程就会去竞争这把锁,当某一个线程竞争到锁之后,如果由于缺乏某些条件导致CPU没有执行该线程,然后该线程释放锁之后还会继续去参与竞争。如果极端情况下一直都是该线程抢到锁,其他线程一直处于阻塞状态,就像没有抢到食物被饿死了一样,这就叫做“线程饿死”现象。这种现象会大大降低程序执行效率,因为其他线程长时间执行不了。
为了解决“线程饿死”现象,就引入了对象等待集——wait(),notify()。当第一次抢到锁的线程发现条件不成熟导致CPU无法执行该线程时,可以通过wait()方法释放锁然后进入阻塞队列,等待通知,等待期间不会再去参与竞争,也就不会去抢夺CPU资源,这样就不会出现“线程饿死”现象了。处于等待状态的线程直到其他线程中调用notify()方法告知其条件成熟之后才会继续参与锁的竞争。
wait()方法的工作过程
- 释放锁(得先有锁才能释放)
- 进入阻塞队列等待通知,这个过程可能会很久
- 当收到通知后,尝试重新获取锁,继续往下执行
下面我们以具体代码为例
public class ThreadDemo16 {//wait和notify方法的使用
public static void main(String[] args) {
Object o = new Object();
Thread t1 = new Thread(){
@Override
public void run() {
synchronized (o){//这个代表让当前线程竞争o这个对象的加锁状态。
try {
System.out.println(("等待开始"));
o.wait();
System.out.println(("等待结束"));
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
};
//线程1用来手动进入等待队列,让程序运行起来之后,线程1会直接进入等待队列
Thread t2 = new Thread(){
@Override
public void run() {
synchronized (o){//谁先执行这段代码谁就先把指定对象的加锁状态设置为true,谁就抢到了锁。
System.out.println(("notify开始"));
o.notify();
System.out.println(("notify结束"));
}
}
};
t1.start();
t2.start();
}
//线程2用来提醒线程1条件达成,可以继续执行线程了。
}
当等待开始之后,线程t1就会进入等待阻塞状态,直到线程t2中调用notify方法发出信号,然后线程t1中接到信号,尝试重新参与竞争,继续执行。此时线程t2还没有释放锁,所以线程t1此时会竞争失败。直到线程t2中执行完才有可能会成功。
需要注意得是wait()方法和notify()方法都是Object类中的方法,所以我们调用这两个方法都需要通过同一个对象。且都需要用synchrnoized关键字包裹起来,因为这两个方法都是需要在有锁的前提下。
在wait()方法的工作过程中,第一步释放锁和第二步等待通知这两个操作是原子性的,避免因为抢占式调度导致线程t1释放所之后,没有进入等待通知的状态就被线程t2抢到锁然后直接发出通知,这样线程t1就永远也收不到通知了。