由于操作系统对线程的调度是随机执行的,且线程之间是抢占式执行的,因此线程之间执行的先后顺序难以预知。但是,有时候在实际开发中,我们希望合理的协调多个线程之间的先后执行顺序。在Java中,wait()方法和notify()方法就是解决该问题的。
1.wait()方法
线程饿死:线程饿死也叫线程饥饿,当多个线程同时竞争一把锁的时候,由于操作系统对线程的调度是随机的,所以当获取锁的线程释放锁之后,接下来哪个线程会拿到锁,这是不确定的。但是由于其他线程都属于在锁上阻塞等待,处于阻塞状态,而当前释放锁的线程处于就绪状态,这个线程还是有很大概率拿到该锁的。这样就会导致其他线程一直吃不到CPU资源,出现线程饿死的现象。
语法形式:
syncronized(锁对象){
锁对象.wait();
}
虽然我们无法干预调度器对于线程的调度,但是我们可以通过调用wait()方法,然后面的逻辑先执行,等后面的逻辑先执行完之后,当前面的逻辑收到通知,再继续执行。
wait()所作的事情
1.让调用wait方法的线程进行等待(将该线程放到等待队列中)
2.释放当前线程所持有的锁
3.满足一定条件被唤醒之后,线程再此重新获取该锁
当一个线程调用对象的wait方法时,该线程会释放该对象的监视器锁,并进入等待队列中等待被唤醒。
注意:由于使用wait()方法会释放锁,所以,再Java中,使用notify()方法之前,我们先加锁,要搭配syncronized使用。否则会报出一个非法锁状态的异常。
如下图
wait()和join()的区别
两个方法都是等,但是join方法必须要等待另一个线程全部执行完之后,代码才能继续走下去,但是wait方法不一定另一个线程全部执行完,只需要下面的逻辑执行到notify方法,代码就可以继续走下去。
syncronized不也是等待吗?
我们要知道,有时尽管写了syncronized,但是它不一定触发等待,因为我们不确定别的线程是否为加锁状态。如果其他线程没有处于加锁状态,那么该线程就直接获取到锁,直接加锁,这个过程中并没有等待。
wait方法的结束条件:
1.其他线程调用notify方法
2.其他线程调用该线程的interrupted方法,会导致wait抛出InterruptedException异常,导致wait被唤醒,同时也导致该线程结束。
3.wait方法也提供了带参数版本,来指定等待的时间。这时只要等待的时间到了,线程就会自动唤醒,不用notify来通知唤醒。
当代码执行到wait方法后,该线程会一直等待下去,那么我们肯定不能让该线程继续等待下去,这时,我们就要用到notify方法去唤醒线程了。
2.notify()方法
语法:
syncronized(锁对象){
锁对象.notify();
}
notify()方法是用来唤醒因为wait方法而阻塞的线程。
注意事项:
1. notify()方法也要搭配syncronized()使用,这是Java中特殊规定的。
2. 使用notify()之前,务必要确保先wait了,否则,notify()方法就没起到唤醒的作用,但是也不会有副作用(抛异常)
3. 如果有多个线程在同一个锁对象上进行了wait,那么,notify()会随机唤醒多个线程中的一个线程。
4. 代码执行到notify()方法后,当前线程并不会马上释放锁,而是要等到执行完notify()方法的线程将程序执行完之后才会释放锁,也就是退出同步代码块(syncronied修饰的代码块)之后才会释放对象锁。
5. wait方法和notify方法锁对象要是一个锁对象,才会起作用
6. 被唤醒的线程不会立即获得对象的监视器锁并继续执行,而是从等待队列中移出,进入与其他线程竞争锁的状态。只有当该线程获得了对象的监视器锁后,才能继续执行wait之后的代码。
例子:
public class Demo9 {public static void main(String[] args) {Object locker=new Object();Thread t1=new Thread(()->{synchronized (locker){System.out.println("t1在wait之前");try {System.out.println("t1执行到wait,释放锁");locker.wait();System.out.println("t1被唤醒");} catch (InterruptedException e) {e.printStackTrace();}}});Thread t2=new Thread(()->{synchronized (locker){System.out.println("t2获取到锁");try {locker.wait();System.out.println("t2被唤醒");} catch (InterruptedException e) {e.printStackTrace();}}});Thread t3=new Thread(()->{synchronized (locker){System.out.println("t3唤醒t1");locker.notify();System.out.println("同步代码块执行完,t3释放锁");}});t1.start();t2.start();t3.start();}
}
根据代码运行结果分析代码逻辑:线程t1启动并获取到锁,进行加锁,接着线程t2也启动,也尝试获取锁,由于t1没有释放锁,t2就阻塞了。接着在线程t1中,执行到wait()方法,t1此时就释放锁,然后线程t3就获取到了锁,当在线程t3中执行到notify()方法,去唤醒t1,当t3中的同步代码块里面的逻辑执行完后,就释放锁 ,由于t1被唤醒需要时间,在这段时间内,由于t1没被唤醒,无法获取到锁,线程t2就获取到了锁,线程t2就会执行到wait,wait方法就会导致线程t2释放锁,这回t1就已经被唤醒了,就会获取到锁,线程t1就会继续执行下去。但是线程t2由于没有notify方法去唤醒,所以它就一直处于睡眠状态,不会被唤醒。
2.1notifyAll()
如果我们想要一次唤醒多个线程在同一个锁对象进行wait,我们就可以使用notifyAll()方法。
如修改上面代码,将线程t1和线程t2全部唤醒。
public static void main(String[] args) {Object locker=new Object();Thread t1=new Thread(()->{synchronized (locker){System.out.println("t1在wait之前");try {locker.wait();System.out.println("t1被notifyAll()唤醒");} catch (InterruptedException e) {e.printStackTrace();}}});Thread t2=new Thread(()->{synchronized (locker){System.out.println("t2在wait之前");try {locker.wait();System.out.println("t2被notifyAll()唤醒");} catch (InterruptedException e) {e.printStackTrace();}}});Thread t3=new Thread(()->{Scanner scanner=new Scanner(System.in);System.out.println("请输入唤醒所有线程");scanner.next();synchronized (locker){locker.notifyAll();System.out.println("notifyall方法后");}});t1.start();t2.start();t3.start();}
注意事项:notifyAll()方法在唤醒所有在等待队列中的线程时,这几个线程是存在锁竞争的,只有一个线程能获得锁,其余线程则会继续阻塞,继续尝试获取锁。
3.wait()和sleep()的区别
共同点:wait和sleep都能使线程暂停一段时间
不同店:
1. wait使Object类中得一个方法,sleep是Thread类中得一个方法;
2. wait方法必须在syncronized修饰的代码块或方法中使用,sleep方法可以在任何位置使用;
3.wait被调用后,当前线程会进入BLOCK状态并释放锁,并可以通过notify和notifyAll方法进行唤醒,sleep方法被调用后,当前线程会进入TIME_WAITING状态,不涉及相关锁的操作。
1.wait方法需要搭配锁来使用,先加锁,之后才能wait,而sleep使用前不需要加锁。
2.如果都是在syncronized内部使用,wait会释放锁,而sleep方法不会释放锁。
4.题目
1.有三个线程,分别只能打印A,B,C,按ABC的顺序打印。
public class Demo18 {public static void main(String[] args) throws InterruptedException {Object locker1=new Object();Object locker2=new Object();Object locker3=new Object();Thread t1=new Thread(()->{for (int i = 0; i < 10; i++) {synchronized (locker1){try {locker1.wait();} catch (InterruptedException e) {e.printStackTrace();}}System.out.print("A");synchronized (locker2){locker2.notify();}}});Thread t2=new Thread(()->{for (int i = 0; i < 10; i++) {synchronized (locker2){try {locker2.wait();} catch (InterruptedException e) {e.printStackTrace();}}System.out.print("B");synchronized (locker3){locker3.notify();}}});Thread t3=new Thread(()->{for (int i = 0; i < 10; i++) {synchronized (locker3){try {locker3.wait();} catch (InterruptedException e) {e.printStackTrace();}}System.out.print("C"+"\n");synchronized (locker1){locker1.notify();}}});t1.start();t2.start();t3.start();Thread.sleep(1000);synchronized (locker1){locker1.notify();}}
}
2.有三个线程,线程名称分别为a,b,c,让每个线程打印自己的名称,并按cba的顺序打印。
public class Demo19 {public static void main(String[] args) throws InterruptedException {Object locker1=new Object();Object locker2=new Object();Object locker3=new Object();Thread a=new Thread(()->{try {synchronized (locker1){locker1.wait();}}catch (InterruptedException e){e.printStackTrace();}System.out.print("a");});Thread b=new Thread(()->{try {synchronized (locker2){locker2.wait();}}catch (InterruptedException e){e.printStackTrace();}System.out.print("b");synchronized (locker1){locker1.notify();}});Thread c=new Thread(()->{try {synchronized (locker3){locker3.wait();}}catch (InterruptedException e){e.printStackTrace();}System.out.print("c");synchronized (locker2){locker2.notify();}});a.start();b.start();c.start();Thread.sleep(1000);//让线程a,b,c进入wait状态synchronized (locker3){locker3.notify();}}
}