Java多线程中wait notify等待唤醒机制详解
作者:IYF.星辰
一.等待唤醒机制简介
由于线程之间是抢占式执行的,因此线程的执行顺序难以预知。但是实际开发中有时候我们希望合理的协调多个线程之间的执行先后顺序。为了完成协调的工作,这里主要设计三个方法:
- wait() / wait(long timeout) : 让当前线程进入等待状态
- notify() / notifyAll(): 唤醒在当前对象上等待的线程
注意:wait,notify,notifyAll都是Object类的方法
二.synchronized与wait()与notify()
- synchronized的含义:
Java中每一个对象都可以成为一个监视器(Monitor), 该Monitor由一个锁(lock), 一个等待队列(waiting queue ), 一个入口队列( entry queue).
对于一个对象的方法, 如果没有synchronized关键字, 该方法可以被任意数量的线程,在任意时刻调用。
对于添加了synchronized关键字的方法,任意时刻只能被唯一的一个获得了对象实例锁的线程调用。
synchronized用于实现多线程的同步操作
- wait()功能:
wait(), notify(), notifyAll() 和 synchonized 需要搭配使用, 用于线程同步
wait()总是在一个循;环中被调用,挂起当前线程来等待一个条件的成立。 Wait调用会一直等到其他线程调用notifyAll()时才返回。
当一个线程在执行synchronized 的方法内部,调用了wait()后, 该线程会释放该对象的锁, 然后该线程会被添加到该对象的等待队列中(waiting queue), 只要该线程在等待队列中, 就会一直处于闲置状态, 不会被调度执行。 要注意wait()方法会强迫线程先进行释放锁操作,所以在调用wait()时, 该线程必须已经获得锁,否则会抛出异常。由于wait()在synchonized的方法内部被执行, 锁一定已经获得, 就不会抛出异常了。
- notify()的功能:
wait(), notify(), notifyAll() 和 synchonized 需要搭配使用, 用于线程同步
当一个线程调用一个对象的notify()方法时, 调度器会从所有处于该对象等待队列(waiting queue)的线程中取出任意一个线程, 将其添加到入口队列( entry queue) 中. 然后在入口队列中的多个线程就会竞争对象的锁, 得到锁的线程就可以继续执行。 如果等待队列中(waiting queue)没有线程, notify()方法不会产生任何作用
notifyAll() 和notify()工作机制一样, 区别在于notifyAll()会将等待队列(waiting queue)中所有的线程都添加到入口队列中(entry queue)
三.等待唤醒机制案例
1.让t1执行wait()方法。
2.此时t2得到锁,再让t2执行notify()方法释放锁。
3.此时t1得到锁,t1会自动从wait()方法之后的代码,继续执行。
4.通过上述流程,我们就可以清楚的看到,wait()和notify()各自是怎么工作的了,也可以知道两者是怎么配合的了。
import java.util.*; public class Main { //创建一个将被两个线程同时访问的共享对象 public static Object loker = new Object(); public static void main(String[] args) throws InterruptedException { Thread t1 = new Thread(()->{ synchronized (loker){ System.out.println("线程一初次获得对象锁,执行过程中调用锁对象的wait()方法~~"); try { loker.wait(); System.out.println("当线程一被唤醒后,后面的代码继续执行"); } catch (InterruptedException e) { e.printStackTrace(); } } System.out.println("线程一运行结束"); },"线程一"); Thread t2 = new Thread(()->{ synchronized (loker){ System.out.println("线程二初次获得对象锁,执行过程中调用锁对象的notify()方法~~"); loker.notify(); System.out.println("唤醒线程一前,后面的代码继续执行~~"); } System.out.println("线程二结束"); },"线程二"); t1.start(); //防止t2优先获得CPU执行权而错过唤醒t1 Thread.sleep(1000); t2.start(); } }
运行结果(运行流程也就是运行的打印结果):
例题一
有三个线程,线程名称分别为:a,b,c。每个线程打印自己的名称。
需要让他们同时启动,并按 c,b,a的顺序打印
代码详解:
import java.util.*; public class Test { public static Object loker1 = new Object(); public static Object loker2 = new Object(); public static void main(String[] args) { System.out.println("打印顺序如下:"); Thread t1 = new Thread(()->{ //为了防止线程A的唤醒没有被线程B接受,这里先让线程A睡一会 try { Thread.sleep(1000); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println(Thread.currentThread().getName()+": C"); synchronized (loker1){ loker1.notify(); } },"线程C"); Thread t2 = new Thread(()->{ synchronized (loker1){ //等待线程A的唤醒 try { loker1.wait(); } catch (InterruptedException e) { e.printStackTrace(); } } System.out.println(Thread.currentThread().getName()+": B"); //线程B唤醒线程C synchronized (loker2){ loker2.notify(); } },"线程B"); Thread t3 = new Thread(()->{ //线程C等待线程A的唤醒 synchronized (loker2){ try { loker2.wait(); } catch (InterruptedException e) { e.printStackTrace(); } } System.out.println(Thread.currentThread().getName()+": A"); },"线程A"); //开启线程 t1.start(); t2.start(); t3.start(); } }
运行结果:
例题二
有三个线程,分别只能打印A,B和C
要求按顺序打印ABC,打印10次
输出示例:
ABC
ABC
ABC
ABC
ABC
ABC
ABC
ABC
ABC
ABC
大致过程:
代码详解:
import java.util.*; public class Demo { private static Object locker1 = new Object(); private static Object locker2 = new Object(); private static Object locker3 = new Object(); public static void main(String[] args) throws InterruptedException { System.out.println("打印结果:"); Thread t1 = new Thread(() -> { try { for (int i = 0; i < 10; i++) { synchronized (locker1) { locker1.wait(); } System.out.print("A"); synchronized (locker2) { locker2.notify(); } } } catch (InterruptedException e) { e.printStackTrace(); } }); Thread t2 = new Thread(() -> { try { for (int i = 0; i < 10; i++) { synchronized (locker2) { locker2.wait(); } System.out.print("B"); synchronized (locker3) { locker3.notify(); } } } catch (InterruptedException e) { e.printStackTrace(); } }); Thread t3 = new Thread(() -> { try { for (int i = 0; i < 10; i++) { synchronized (locker3) { locker3.wait(); } System.out.println("C"); synchronized (locker1) { locker1.notify(); } } } catch (InterruptedException e) { e.printStackTrace(); } }); t1.start(); t2.start(); t3.start(); //让三个线程都拿到锁 Thread.sleep(1000); // 从线程 t1 启动 synchronized (locker1) { locker1.notify(); } } }
运行结果:
四.什么时候释放锁——wait()与notify()
由于等待一个锁定线程只有在获得这把锁之后,才能恢复运行,所以让持有锁的线程在不需要锁的时候及时释放锁是很重要的。在以下情况下,持有锁的线程会释放锁:
1.执行完同步代码块。
2.在执行同步代码块的过程中,遇到异常而导致线程终止。
3.在执行同步代码块的过程中,执行了锁所属对象的wait()方法,这个线程会释放锁,进行对象的等待池。
除了以上情况外,只要持有锁的对象还没有执行完同步代码块,就不会释放锁。因此在以下情况下,线程不会释放锁:
1.在执行同步代码块的过程中,执行了Thread.sleep()方法,当前线程放弃CPU,开始睡眠,在睡眠中不会释放锁。
2.在执行同步代码块的过程中,执行了Thread.yield()方法,当前线程放弃CPU,但不会释放锁。
3.在执行同步代码块的过程中,其他线程执行了当前对象的suspend()方法,当前线程被暂停,但不会释放锁。但Thread类的suspend()方法已经被废弃。
到此这篇关于Java多线程中wait notify等待唤醒机制详解的文章就介绍到这了,更多相关Java等待唤醒机制内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!