JUC-三种等待唤醒方式

Posted 玩葫芦的卷心菜

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了JUC-三种等待唤醒方式相关的知识,希望对你有一定的参考价值。

一、synchronized的wait和notify

wait方法会释放对象锁资源后进入等待队列,等待被唤醒
notify会唤醒等待队列中的单个线程

	static Object objectLock=new Object();'
	new Thread(()->{
            synchronized (objectLock){
                System.out.println(Thread.currentThread().getName()+" come in");
                try {
                    objectLock.wait();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                System.out.println(Thread.currentThread().getName()+" 被唤醒");
            }
        },"A").start();

        new Thread(()->{
            synchronized (objectLock){
                System.out.println(Thread.currentThread().getName()+"唤醒");
                 objectLock.notify();
            }
        },"B").start();

特点:
需要搭配synchronized使用,否则会报异常
需要先等待后唤醒,否则会一直等待

二、Lock的await和signal

await会释放掉锁资源,然后当前线程进入等待队列
signal就是会唤醒等待队列中的单个线程

	static Lock lock=new ReentrantLock();
    static Condition condition=lock.newCondition();
		new Thread(()->{
            lock.lock();
            try {
                System.out.println(Thread.currentThread().getName() + " come in");
                try {
                    condition.await();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                System.out.println(Thread.currentThread().getName() + " 被唤醒");
            } finally {
                lock.unlock();
            }
        },"A").start();

        new Thread(()->{
            lock.lock();
            try {
                System.out.println(Thread.currentThread().getName() + "唤醒");
                condition.signal();
            } finally {
                lock.unlock();
            }
        },"B").start();

特点:
需要搭配Lock使用,否则会报异常
同样需要先等待后唤醒

三、LookSupport

原理:每个线程都有一个许可证
LookSupport有两个方法

  • park:等待/.阻塞,当前线程的许可证-1(消费当前线程许可证),如果许可证为0就阻塞当前线程,只有调用unpark生产许可证供park消费或者线程中断的时候,才会从park返回
  • unpark:唤醒,为指定线程生产一个许可证,许可证+1

因为许可证的上限是1,所以多次unpark无效
许可证有两个信号量:1和0

     Thread ta=new Thread(()->{
//            try {
//                TimeUnit.SECONDS.sleep(1);
//            } catch (InterruptedException e) {
//                e.printStackTrace();
//            }
            System.out.println(Thread.currentThread().getName()+" come in");
            LockSupport.park();//消费许可证,如果没有就阻塞
            System.out.println(Thread.currentThread().getName()+" 被唤醒");
        },"A");

        ta.start();

        new Thread(()->{
            System.out.println(Thread.currentThread().getName()+"唤醒A");
           LockSupport.unpark(ta);//对当前线程的许可证+1,上限为1
        },"B").start();

面试题

为什么LockSupport可以先唤醒后等待?

因为唤醒操作unpark生产了许可证
等待操作park可以直接消费掉该许可证,不用阻塞

为什么等待两次唤醒两次,最终线程还是会阻塞?

	 Thread ta=new Thread(()->{
            System.out.println(Thread.currentThread().getName()+" come in");
            LockSupport.park();//消费许可证,如果没有就阻塞
            LockSupport.park();
            System.out.println(Thread.currentThread().getName()+" 被唤醒");
        },"A");

        ta.start();

        new Thread(()->{
            System.out.println(Thread.currentThread().getName()+"唤醒A");
            LockSupport.unpark(ta);//对当前线程的许可证+1,上限为1
            LockSupport.unpark(ta);
        },"B").start();

A线程第一次park,没有许可证阻塞,没有继续执行
线程B连续两次unpark,但是许可证上限为1,所以当前许可证为1
A线程检测到许可证,park消费一个,往下执行,park没有许可证就阻塞了,但是没有线程生产许可证了

特点

不需要在意线程的等待唤醒顺序
不需要维护同步对象和获得锁,实现了线程的解耦

以上是关于JUC-三种等待唤醒方式的主要内容,如果未能解决你的问题,请参考以下文章

JUC并发编程 共享模式之工具 JUC CountdownLatch(倒计时锁) -- CountdownLatch应用(等待多个线程准备完毕( 可以覆盖上次的打印内)等待多个远程调用结束)(代码片段

JUC - 线程中断与线程等待唤醒(LockSupport)

JUC 并发类概览

生产者与消费者案例-虚假唤醒

Day275.Thread和Object类中的重要方法 -Juc

JUC学习