Java多线程的3种等待唤醒机制

编程入门 行业动态 更新时间:2024-10-24 06:26:55

Java<a href=https://www.elefans.com/category/jswz/34/1767532.html style=多线程的3种等待唤醒机制"/>

Java多线程的3种等待唤醒机制

Java多线程 3 种等待、唤醒机制

  • Object类中的wait、notify方法配置synchronized关键字;
  • Condition接口中的await、signal方法配合Lock;
  • LockSupport类中的park、unpark方法(无需锁

一、Object类中的wait和notify方法实现线程的等待和唤醒

注意

  • 不能脱离synchronized代码块使用,否则会抛出IllegalMonitorStateException异常;
  • 先wait后notify、notifyAll,等待中的线程才能被唤醒,顺序不能改变
public class LockDemo {static Object object = new Object();public static void main(String[] args) {new Thread(() -> {synchronized (object) {System.out.println(Thread.currentThread().getName() + "==>线程Come in");try {// 使当前线程等待object.wait();} catch (InterruptedException e) {e.printStackTrace();}System.out.println(Thread.currentThread().getName() + "==>线程被唤醒");}}, "A").start();new Thread(() -> {synchronized (object) {System.out.println(Thread.currentThread().getName() + "==>通知线程");// 使当前线程唤醒object.notifyAll();}}, "B").start();}
}

我们来看下上面代码的执行结果,看起来很和谐:

A==>线程Come in
B==>通知线程
A==>线程被唤醒

我们来将上面代码动下手脚,注释掉10、23代码看下wait/notify脱离了Synchronized会出现什么?
可以看到报了异常,说明wait/notify不能够脱离Synchronized代码块

public class LockDemo {static Object object = new Object();public static void main(String[] args) {new Thread(() -> {
//            synchronized (object) {System.out.println(Thread.currentThread().getName() + "==>线程Come in");try {// 使当前线程等待object.wait();} catch (InterruptedException e) {e.printStackTrace();}System.out.println(Thread.currentThread().getName() + "==>线程被唤醒");
//            }}, "A").start();new Thread(() -> {
//            synchronized (object) {System.out.println(Thread.currentThread().getName() + "==>通知线程");// 使当前线程唤醒object.notifyAll();
//            }}, "B").start();}
}

我们再来看一下,线程A先sleep 2秒,让线程B先执行notify方法,会发生什么?

public class LockDemo {static Object object = new Object();public static void main(String[] args) {new Thread(() -> {try {TimeUnit.SECONDS.sleep(2);} catch (InterruptedException e) {throw new RuntimeException(e);}synchronized (object) {System.out.println(Thread.currentThread().getName() + "==>线程Come in");try {// 使当前线程等待object.wait();} catch (InterruptedException e) {e.printStackTrace();}System.out.println(Thread.currentThread().getName() + "==>线程被唤醒");}}, "A").start();new Thread(() -> {synchronized (object) {System.out.println(Thread.currentThread().getName() + "==>通知线程");// 使当前线程唤醒object.notifyAll();}}, "B").start();}
}

 线程A由于没有被唤醒,阻塞住了

二、Condition接口中的await和single方法实现线程的等待和唤醒

注意

  • 必须配合lock()方法使用,否则抛出IllegalMonitorStateException异常
  • 等待唤醒调用顺序不能改变
public class LockDemo {static Object object = new Object();// 创建可重入锁static Lock lock = new ReentrantLock();static Condition condition = lock.newCondition();public static void main(String[] args) {new Thread(() -> {lock.lock();try {System.out.println(Thread.currentThread().getName() + "==>线程Come in");// 使当前线程等待condition.await();} catch (InterruptedException e) {e.printStackTrace();} finally {lock.unlock();}System.out.println(Thread.currentThread().getName() + "==>线程被唤醒");}, "A").start();new Thread(() -> {lock.lock();try {System.out.println(Thread.currentThread().getName() + "==>通知线程");// 使当前线程唤醒condition.signal();} catch (Exception e) {e.printStackTrace();} finally {lock.unlock();}}, "B").start();}
}

看下上面代码的执行结果:

A==>线程Come in
B==>通知线程
A==>线程被唤醒

我们来将上面代码动下手脚,注释掉9 17 24 32行代码看下wait/notify脱离了Synchronized会出现什么?
可以看到报了异常,说明wait/notify不能够脱离Synchronized代码块

public class LockDemo {static Object object = new Object();// 创建可重入锁static Lock lock = new ReentrantLock();static Condition condition = lock.newCondition();public static void main(String[] args) {new Thread(() -> {
//            lock.lock();try {System.out.println(Thread.currentThread().getName() + "==>线程Come in");// 使当前线程等待condition.await();} catch (InterruptedException e) {e.printStackTrace();} finally {
//                lock.unlock();}System.out.println(Thread.currentThread().getName() + "==>线程被唤醒");}, "A").start();new Thread(() -> {//            lock.lock();try {System.out.println(Thread.currentThread().getName() + "==>通知线程");// 使当前线程唤醒condition.signal();} catch (Exception e) {e.printStackTrace();} finally {
//                lock.unlock();}}, "B").start();}
}

我们再来看一下使线程A暂停三秒会发生什么?
线程B先执行了,没有代码将线程A的await状态进行唤醒

public class LockDemo {static Object object = new Object();// 创建可重入锁static Lock lock = new ReentrantLock();static Condition condition = lock.newCondition();public static void main(String[] args) {new Thread(() -> {try {TimeUnit.SECONDS.sleep(2);} catch (InterruptedException e) {throw new RuntimeException(e);}lock.lock();try {System.out.println(Thread.currentThread().getName() + "==>线程Come in");// 使当前线程等待condition.await();} catch (InterruptedException e) {e.printStackTrace();} finally {lock.unlock();}System.out.println(Thread.currentThread().getName() + "==>线程被唤醒");}, "A").start();new Thread(() -> {lock.lock();try {System.out.println(Thread.currentThread().getName() + "==>通知线程");// 使当前线程唤醒condition.signal();} catch (Exception e) {e.printStackTrace();} finally {lock.unlock();}}, "B").start();}
}

同样,线程A会被阻塞

三、LockSupport类中的park等待和unpark唤醒

        LockSupport提供park()和unpark()方法实现阻塞和唤醒线程的过程。
        LockSupport和每一个使用它的线程之间有一个许可(permit)进行关联,permit有0和1两种状态,默认为0,即无许可证状态。
        调用一次unpark方法,permit加1变成1。每次调用park方法都会检查许可证状态,如果为1,则消耗掉permit(1 -> 0)并立刻返回;如果为0,则进入阻塞状态。permit最多只有一个,重复调用unpark也不会累积permit。

public class LockDemo {static Object object = new Object();// 创建可重入锁static Lock lock = new ReentrantLock();static Condition condition = lock.newCondition();public static void main(String[] args) {Thread a = new Thread(() -> {System.out.println(Thread.currentThread().getName() + "==>Come in");LockSupport.park();//阻塞当前线程System.out.println(Thread.currentThread().getName() + "==>被唤醒");});a.setName("A");a.start();new Thread(() -> {LockSupport.unpark(a);System.out.println(Thread.currentThread().getName() + "==>通知");}, "B").start();}
}

看下上面代码的执行结果:

A==>Come in
B==>通知
A==>被唤醒

下面给线程A进行等待3S,看会不会像一,二两个例子出现错误情况:
得出结论是:
  因为unpark获得了一个凭证,之后再调用park方法,就可以名正言顺的凭证消费,故不会阻塞。

public class LockDemo {static Object object = new Object();// 创建可重入锁static Lock lock = new ReentrantLock();static Condition condition = lock.newCondition();public static void main(String[] args) {Thread a = new Thread(() -> {try {TimeUnit.SECONDS.sleep(2);} catch (InterruptedException e) {throw new RuntimeException(e);}System.out.println(Thread.currentThread().getName() + "==>Come in");LockSupport.park();//阻塞当前线程System.out.println(Thread.currentThread().getName() + "==>被唤醒");});a.setName("A");a.start();new Thread(() -> {LockSupport.unpark(a);System.out.println(Thread.currentThread().getName() + "==>通知");}, "B").start();}
}

执行结果,不阻塞

        LockSupport是用来创建锁和其他同步类的基本线程阻塞原语
        LockSupport是一个线程阻塞工具类,所有的方法都是静态方法,可以让线程在任意位置阻塞,阻塞之后也有对应的唤醒方法。归根结底,LockSupport调用的Unsafe中的native代码。
 
        LockSupport提供park()和unpark()方法实现阻塞线程和解除线程阻塞的过程
LockSupport和每个使用它的线程都有一个许可(permit)关联。permit相当于1,0的开关,默认是0,调用一次unpark就加1变成1,调用一次park会消费permit,也就是将1变成o,同时park立即返回。如再次调用park会变成阻塞(因为permit为零了会阻塞在这里,一直到permit变为1),这时调用unpark会把permit置为1。每个线程都有一个相关的permit, permit最多只有一个,重复调用unpark也不会积累凭证。
 
线程阻塞需要消耗凭证(permit),这个凭证最多只有1个。当调用park方法时
    *如果有凭证,则会直接消耗掉这个凭证然后正常退出;
    *如果无凭证,就必须阻塞等待凭证可用;
而unpark则相反,它会增加一个凭证,但凭证最多只能有1个,累加无效。

更多推荐

Java多线程的3种等待唤醒机制

本文发布于:2023-11-15 12:39:27,感谢您对本站的认可!
本文链接:https://www.elefans.com/category/jswz/34/1599789.html
版权声明:本站内容均来自互联网,仅供演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系,我们将在24小时内删除。
本文标签:多线程   机制   Java

发布评论

评论列表 (有 0 条评论)
草根站长

>www.elefans.com

编程频道|电子爱好者 - 技术资讯及电子产品介绍!