07 - 线程的等待唤醒机制(JUC系列)

目录

一、关于LockSupport

二、回顾线程等待唤醒机制

2.1 让线程等待和唤醒的三种方法

2.2 代码演示


一、关于LockSupport

什么是LockSupport?

        官方Api文档的定义为:用于创建锁和其他同步类的基本线程阻塞原语。

        乍一看,有些懵。我们通过文档发现,LockSupport类没有提供公共的构造器,意味着不能实例化。我们可以看一下它所提供的方法。其中最重要的就是park()和unpark()方法。它们的作用如下所示。

        简单用一句话概括就是:LockSupport中park()和unpark()方法的作用分别是阻塞线程和解除阻塞线程。

LockSupport机制,其实是对线程等待唤醒机制的另一种优化和提升。

二、回顾线程等待唤醒机制

2.1 让线程等待和唤醒的三种方法

方法一

使用Object类中的wait()方法让线程等待,使用Object中的notify()方法唤醒线程。

方法二

使用JUC包中的Condition的await()方法让线程等待,使用signal()方法唤醒线程。

方法一和方法二的注意点:

  •  wait():一旦执行此方法,当前线程就进入阻塞状态,并释放同步监视器。 
  • notify():一旦执行此方法,就会唤醒被wait的一个线程。如果有多个线程被wait,就唤醒优先级高的那个。
  • notifyAll():一旦执行此方法,就会唤醒所有被wait的线程。
  • wait()/notify()/notifyAll()三个方法都必须在同步方法或同步代码块中执行,三个方法的调用者必须是当前的锁对象(即当前同步方法或同步代码块中的同步监视器)。否则会报IllegalMonitorStateException异常。
  • 如果先notify()执行再执行wait()方法,等待的线程无法被唤醒。(因为没线程唤醒了)
  • await()/signal()/signalAll()也一样。只是API不同,底层实现原理是一样的。必须在lock锁中执行,否则会报异常。也必须先等待后唤醒才能被唤醒。

方法三

LockSupport类使用了一种名为Permit(许可)的概念来做到阻塞和唤醒线程的功能。

主要使用LockSupport的park()和unpark(Thread thread)方法。

原理:permit许可证默认没有不能放行,所以一开始调用park()方法当前线程就会阻塞,直到别的线程给当前线程发放permit,park()方法才会被唤醒。

它相比之下的好处是:

不用在synchronized和lock中执行。

先唤醒锁,然后等待。这种情况下也能唤醒等待。此时的park()方法相当于等待不起作用。

问题:为什么可以先唤醒线程后阻塞线程?
        因为unpark获取了一个凭证,之后再调用park方法就名正言顺的凭证消费。故不会阻塞线程。

问题:为什么唤醒两次后阻塞两次,但最终还是会阻塞线程?
        因为凭证的数量最多是1.连续两次调用unpark和调用一次unpark效果是一样的,只会增加一个凭证。而调用两次park却需要消费两个凭证,凭证不够。不能放行。

2.2 代码演示

为了便于理解,这里针对以上的三种等待唤醒机制进行代码演示。

关于wait()和notify()

/**
 * 代码解释:
 * 一开始t1线程启动,然后被wait(),阻塞当前线程,使得线程停滞,同时释放了同步监视器
 * 然后等待了几秒后,t2线程启动,t2线程notify()唤醒了当前被wait()的这个线程。
 */

public class LockSupportTest {
    public static void main(String[] args) {
        Object object = new Object();
        new Thread(()->{
            synchronized (object){
                System.out.println(Thread.currentThread().getName() + "\t" + "---- 进来了");
                try {
                    object.wait();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                System.out.println(Thread.currentThread().getName() + "\t" + "---- 被唤醒");
            }
        },"t1").start();

        // 暂停几秒
        try {Thread.sleep(2000);} catch (InterruptedException e) {e.printStackTrace();}

        new Thread(()->{
            synchronized (object){
                object.notify();
                System.out.println(Thread.currentThread().getName() + "\t" + "---- 发出通知");
            }
        },"t2").start();

    }
}

  


/**
 * 代码解释:
 * 1.wait(),notify(),notifyAll()三个方法必须使用在同步代码块或同步方法中。
 * 2.wait(),notify(),notifyAll()三个方法的调用者必须是同步代码块或同步方法中的同步监视器。
 *   否则,会出现IllegalMonitorStateException异常
 */

public class LockSupportTest {
    public static void main(String[] args) {
        Object object = new Object();
        new Thread(()->{
            //synchronized (object){
                System.out.println(Thread.currentThread().getName() + "\t" + "---- 进来了");
                try {
                    object.wait();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                System.out.println(Thread.currentThread().getName() + "\t" + "---- 被唤醒");
            //}
        },"t1").start();

        // 暂停几秒
        try {Thread.sleep(2000);} catch (InterruptedException e) {e.printStackTrace();}

        new Thread(()->{
            //synchronized (object){
                object.notify();
                System.out.println(Thread.currentThread().getName() + "\t" + "---- 发出通知");
            //}
        },"t2").start();

    }
}


/**
 * 代码解释:
 * t1线程先进来,但是需要执行了几秒,还没有获取锁
 * 然后紧随其后t2线程进来,唤醒了object锁对象,--> 实际上还当前锁对象还没被wait()
 * 当我们t1线程执行几秒结束后,执行同步代码块的内容,
 * 此时t2线程肯定执行完了代码,释放了锁,因此t1线程可以进入自己的代码块内。
 * 但是t1线程接着需要执行object.wait();方法。
 * 由于t2线程已经结束了,所以没法去唤醒t1线程,因此t1线程就一致阻塞着,等待唤醒。
 */

public class LockSupportTest {
    public static void main(String[] args) {
        Object object = new Object();
        new Thread(()->{
            // 暂停几秒
            try {Thread.sleep(2000);} catch (InterruptedException e) {e.printStackTrace();}
            synchronized (object){
                System.out.println(Thread.currentThread().getName() + "\t" + "---- 进来了");
                try {
                    object.wait();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                System.out.println(Thread.currentThread().getName() + "\t" + "---- 被唤醒");
            }
        },"t1").start();



        new Thread(()->{
            synchronized (object){
                object.notify();
                System.out.println(Thread.currentThread().getName() + "\t" + "---- 发出通知");
            }
        },"t2").start();

    }
}

同理,await()和signal()

package com.hssy.jucstudy.juc;

import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.ReentrantLock;

/**
 * 代码解释:
 *  同理,await()方法和signal()方法也是一样的道理。
 */
public class LockSupportDemo {
    public static void main(String[] args) {
        ReentrantLock lock = new ReentrantLock();
        Condition condition = lock.newCondition();

        new Thread(()->{
            lock.lock();
            try {
                System.out.println(Thread.currentThread().getName() + "\t" + "进来了");
                condition.await();
                System.out.println(Thread.currentThread().getName() + "\t" + "被唤醒");
            } catch (InterruptedException e) {
                e.printStackTrace();
            } finally {
                lock.unlock();
            }

        },"t1").start();

        try {Thread.sleep(2000);} catch (InterruptedException e) {e.printStackTrace();}

        new Thread(()->{
            lock.lock();
            try {
                condition.signal();
                System.out.println(Thread.currentThread().getName() + "\t" + "发出通知");
            } finally {
                lock.unlock();
            }
        },"t2").start();
    }
}

 LockSupport的park()和unpark()

public class LockSupportStudy {
    public static void main(String[] args) {
        Thread t1 = new Thread(() -> {
            System.out.println(Thread.currentThread().getName() + "\t" + "进来了");
            LockSupport.park();
            System.out.println(Thread.currentThread().getName() + "\t" + "被唤醒");
        }, "t1");
        t1.start();

        try {Thread.sleep(2000);} catch (InterruptedException e) {throw new RuntimeException(e);}

        new Thread(()->{
            LockSupport.unpark(t1);
            System.out.println(Thread.currentThread().getName() + "\t" + "发出通知");
        },"t2").start();
    }
}

package com.hssy.jucstudy.juc;

import java.util.concurrent.locks.LockSupport;

/**
 * 代码解释:
 *  t2线程先唤醒锁,也不会造成t1线程阻塞
 */
public class LockSupportStudy {
    public static void main(String[] args) {
        Thread t1 = new Thread(() -> {
            try {Thread.sleep(2000);} catch (InterruptedException e) {throw new RuntimeException(e);}
            System.out.println(Thread.currentThread().getName() + "\t" + "进来了");
            LockSupport.park();
            System.out.println(Thread.currentThread().getName() + "\t" + "被唤醒");
        }, "t1");
        t1.start();


        new Thread(()->{
            LockSupport.unpark(t1);
            System.out.println(Thread.currentThread().getName() + "\t" + "发出通知");
        },"t2").start();
    }
}

 


package com.hssy.jucstudy.juc;

import java.util.concurrent.locks.LockSupport;

/**
 * 代码解释:
 *  许可证不会累计,最多只有1个
 */
public class LockSupportStudy {
    public static void main(String[] args) {
        Thread t1 = new Thread(() -> {
            try {Thread.sleep(2000);} catch (InterruptedException e) {throw new RuntimeException(e);}
            System.out.println(Thread.currentThread().getName() + "\t" + "进来了");
            LockSupport.park();
            LockSupport.park();
            System.out.println(Thread.currentThread().getName() + "\t" + "被唤醒");
        }, "t1");
        t1.start();


        new Thread(()->{
            LockSupport.unpark(t1);
            LockSupport.unpark(t1);
            LockSupport.unpark(t1);
            LockSupport.unpark(t1);
            LockSupport.unpark(t1);
            System.out.println(Thread.currentThread().getName() + "\t" + "发出通知");
        },"t2").start();
    }
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

何苏三月

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值