wait
wait(等待)/notify(通知)
线程在操作系统上的调度是随机的
多个线程,需要控制线程之间执行某个逻辑的先后顺序,就可以让后执行的逻辑,使用wait,先执行线程,完成某些逻辑之后,通过notify唤醒对应的wait
另外,通过wait notify也是为了解决"线程饿死"问题针对上述问题,同样也可以使用wait/notify来解决
让1号滑稽,拿到锁的时候进行判定
判定当前能否执行"取钱”操作.如果能护行,就正常执行
如果不能执行呢,就需要主动释放锁并且"阻塞等待"(通过调用wit),此时这个线程就不会在后续参与锁的竞争了,一直阻塞到"取钱”的条件具备了,此时,再由其他线程通过通知机制(notify)唤醒这个线程
public class Demo23 { public static void main(String[] args) throws InterruptedException { Object obj = new Object(); System.out.println("wait 之前"); obj.wait(); System.out.println("wait 之后"); } }
public class Demo23 { public static void main(String[] args) throws InterruptedException { Object obj = new Object(); System.out.println("wait 之前"); synchronized (obj) { obj.wait(); } System.out.println("wait 之后"); } }
由于代码中没有notify,所以wait一直持续等待下去~~
notify
wait使调用的线程进入阻塞,notify则是通知wait的线程被唤醒(另一个线程调用的)
被唤醒的wait就会重新竞争锁,并且在拿到锁之后,再继续执行
wait一共做了三件事
1)释放锁
2)进入阻塞等待,准备接受通知
3)收到通知之后,被唤醒,并且重新尝试获取锁
import java.util.Scanner; public class Demo24 { private static Object locker = new Object(); public static void main(String[] args) { Thread t1 = new Thread(() -> { synchronized (locker) { System.out.println("t1 wait 之前"); try { locker.wait(); } catch (InterruptedException e) { throw new RuntimeException(e); } System.out.println("t1 wait 之后"); } }); Thread t2 = new Thread(() -> { synchronized (locker) { System.out.println("t2 notify 之前"); Scanner sc = new Scanner(System.in); sc.next();//此处用户输入啥都行,主要是通过这个 next ,构造“阻塞” synchronized (locker){ locker.notify(); } System.out.println("t2 notify 之后"); } }); t1.start(); t2.start(); } }
一定要确保持有锁才能谈释放
notify唤醒wait的线程,假设多个线程,如果多个线程都在同一个对象上wait,此时notify是如何唤醒呢?
随机唤醒其中的一个线程import java.util.Scanner; public class Demo25 { private static Object locker = new Object(); public static void main(String[] args) { Thread t1 = new Thread(() -> { synchronized (locker) { System.out.println("t1 wait 之前"); try { locker.wait(); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println("t1 wait 之后"); } }); Thread t2 = new Thread(() -> { synchronized (locker) { System.out.println("t2 wait 之前"); try { locker.wait(); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println("t2 wait 之后"); } }); Thread t3 = new Thread(() -> { synchronized (locker) { System.out.println("t3 wait 之前"); try { locker.wait(); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println("t3 wait 之后"); } }); Thread t4 = new Thread(() -> { Scanner sc = new Scanner(System.in); sc.next(); System.out.println("t4 notify 之前"); synchronized (locker) { locker.notify(); } System.out.println("t4 notify 之后"); }); t1.start(); t2.start(); t3.start(); t4.start(); } }
和notify相对,还有一个操作,notifyAll唤醒所有等待的线程
唤醒是唤醒一个,还是唤醒所有,大部分的情况,使用唤醒一个的
一个一个唤醒(多执行几次notify)整个程序执行过程是比较有序的
如果一下唤醒所有,这些被唤醒的线程,就无序的竞争锁
如果对方没有线程wait,或者只有一个线程wait,但是另一个线程notify多次,会咋样呢?
不会咋样,notify通知的时候,如果无人wait,不会有任何副作用import java.util.Scanner; public class Demo25 { private static Object locker = new Object(); public static void main(String[] args) { Thread t1 = new Thread(() -> { synchronized (locker) { System.out.println("t1 wait 之前"); try { locker.wait(); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println("t1 wait 之后"); } }); Thread t2 = new Thread(() -> { synchronized (locker) { System.out.println("t2 wait 之前"); try { locker.wait(); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println("t2 wait 之后"); } }); Thread t3 = new Thread(() -> { synchronized (locker) { System.out.println("t3 wait 之前"); try { locker.wait(); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println("t3 wait 之后"); } }); Thread t4 = new Thread(() -> { Scanner sc = new Scanner(System.in); sc.next(); System.out.println("t4 notify 之前"); synchronized (locker) { locker.notify(); locker.notify(); locker.notify(); locker.notify(); locker.notify(); locker.notify(); locker.notify(); locker.notify(); locker.notify(); locker.notify(); } System.out.println("t4 notify 之后"); }); t1.start(); t2.start(); t3.start(); t4.start(); } }
wait 和 sleep 的对⽐(⾯试题)
wait默认也是"死等"
1. wait 需要搭配 synchronized 使⽤,sleep 不需要.
wait还提供带参数的版本,指定超时时间
如果wait达到了最大的时间,还没有notify就不会继续等待了,而是直接继续执行.
wait(1000)和sleep (1000)看起来就有点相似了~~
wait和sleep有本质区别的
使用wait的目的是为了提前唤醒。sleep就是固定时间的阻塞,不涉及到唤醒的
虽然sleep可以被Interrupt唤醒,Interrupt操作,表示的意思不是"唤醒"而是要终止线程了
wait必须要搭配synchronized使用,并且wait会先释放锁,同时进行等待
sleep和锁无关.如果不加锁,sleep可以正常使用,如果加了锁,sleep操作不会释放锁"抱着锁"一起睡,其他线程无法拿到锁的
总结下:2. wait 是 Object 的⽅法 sleep 是 Thread 的静态⽅法.
练习
创建三个线程,分别打印A,B,C 通过wait、notify约定线程的打印顺序,先打印A,然后B,最后C
public class Demo26 { public static void main(String[] args) { Thread t1 = new Thread(() -> { System.out.println("A"); }); Thread t2 = new Thread(() -> { System.out.println("B"); }); Thread t3 = new Thread(() -> { System.out.println("C"); }); t1.start(); t2.start(); t3.start(); } }
public class Demo26 { private static Object locker1 = new Object(); private static Object locker2 = new Object(); public static void main(String[] args) { Thread t1 = new Thread(() -> { System.out.println("A"); synchronized (locker1) { locker1.notify(); } }); Thread t2 = new Thread(() -> { synchronized (locker1) { try { locker1.wait(); } catch (InterruptedException e) { e.printStackTrace(); } } System.out.println("B"); synchronized (locker2) { locker2.notify(); } }); Thread t3 = new Thread(() -> { synchronized (locker2) { try { locker2.wait(); } catch (InterruptedException e) { e.printStackTrace(); } } System.out.println("C"); }); t1.start(); t2.start(); t3.start(); } }
如果这个代码中,是先执行t2的wait,后执行t1的notify,代码的逻辑是一切顺利的
(大概率是这样的,因为t1的打印需要消耗不少时间)
但是实际上,存在这样的概率,t1先执行了打印和notify,然后t2才执行wait,意味着通知来的早了,t2错过了,t2的wait就无人唤醒了只需要在t1中加上sleep