1.LockSupport是什么?

  • LockSupport是用来创建锁和其他同步类的基本线程阻塞原语。
  • LockSupport中的park()和unPark()的作用分别是阻塞线程和解除阻塞线程

2.线程等待唤醒机制(wait/notify)

  • 1.三种让线程等待和唤醒的方法
    • 方式1:使用Object中的wait()方法让线程等待,使用Object中的notify()方法唤醒线程。
    • 方式2:使用JUC包中Condition的await()方法让线程等待,使用signal()方法唤醒线程。
    • 方式3:LockSupport类可以阻塞当前线程以唤醒指定被阻塞的线程。
  • 2.Object类中的wait和notify方法实现线程等待和唤醒
    • 问题1:Object类中的wait,notify,notifyAll用于线程等待和唤醒的方法,都必须在synchronized内部执行(必须用到关键字synchronized)。
    • 问题2:将notify放在wait方法前面,程序无法执行,无法唤醒。
  • 3.Condition接口中的await后signal方法实现线程的等待和唤醒
    会出现同Object类中的wait和notify方法实现线程等待和唤醒一样的两个问题。
  • 4.传统的synchronized和Lock实现等待唤醒通知的约束。
    线程先要获得并持有锁,必须在锁块(synchronized或lock)中。
    必须要先等待后唤醒,线程才能够被唤醒。
  • 5.LockSupport类中的park等待和unpark唤醒
    是什么:通过park()和unpark(thread)方法来实现阻塞和唤醒线程的操作。
    官网解释:LockSupport是用来创建锁和其他同步类的基本线程阻塞原语。
    LockSupport类使用了一种名为Permit(许可)的概念来做到阻塞和唤醒线程的功能,每个线程都有一个许可(permit),permit只有两个值1和0,默认为0。可以把许可看成是一种(0,1)信号量(Semaphore),但与Semaphore不同的是,许可的累加上限是1。

方法调用:

  • 阻塞:park()/park(Object blocker),阻塞当前线程/阻塞传入的具体线程。
    调用LockSupport.park()时
public static void(){
 UNSAFE.park(false,0L);
}

permit默认是0,所以一开始调用park()方法,当前线程就会阻塞,直到别的线程将当前线程的permit设置为1时,park方法会被唤醒,然后会将permit再次设置为0并返回。

  • 唤醒:unpark(Thread thread),唤醒处于阻塞状态的指定线程。
    调用LockSupport.unpark(thread)时
public static void unpark(Thread thread){
    if(thread !=null)
    UNSAFE.unpark(thread);
}

调用unpark(thread)方法后,就会将thread线程的许可permit设置成1(注意多次调用unpark方法,不会累加,permit值还是1)会自动唤醒thread线程,即之前阻塞中的LockSupport.park()方法会立即返回。
代码:
正常+无锁块要求
之前错误的先唤醒后等待LockSupport照样支持

public class LockSupportDemo {

    public static void main(String[] args) {

        Thread a = new Thread(() -> {

            System.out.println(Thread.currentThread().getName() + "\t come in");
            LockSupport.park();
            System.out.println(Thread.currentThread().getName() + "\t 唤醒");
        }, "a");

        a.start();

        Thread b = new Thread(() -> {

            try {
                TimeUnit.SECONDS.sleep(3);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            LockSupport.unpark(a);
            System.out.println(Thread.currentThread().getName() + "\t 通知了");
        }, "b");

        b.start();
    }
}

运行结果:

a	 come in
b	 通知了
a	 唤醒
public class LockSupportDemo {

    public static void main(String[] args) {

        Thread a = new Thread(() -> {

            try {
                TimeUnit.SECONDS.sleep(3);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }

            System.out.println(Thread.currentThread().getName() + "\t come in");
            LockSupport.park();
            System.out.println(Thread.currentThread().getName() + "\t 唤醒");
        }, "a");

        a.start();

        Thread b = new Thread(() -> {


            LockSupport.unpark(a);
            System.out.println(Thread.currentThread().getName() + "\t 通知了");
        }, "b");

        b.start();
    }
}

运行结果:

b	 通知了
a	 come in
a	 唤醒

原理:
归根结底,LockSupport调用的Unsafe中的native代码
LockSupport提供park()和unpark()方法实现阻塞线程和解除线程阻塞的过程
LockSupport和每个使用它的线程都有一个许可(permit)关联。permit相当于1,0的开关,默认是0,调用一次unpark就加1变成1,调用一次park就会消费permit,也就是将1变成0,同时park立即返回。如果再次调用park会变成阻塞(因为permit为0了会阻塞在这里,一直到permit变为1),这时调用unpark会把permit置为1。每个线程都有一个相关的permit,permit最多只有一个,重复调用unpark也不会累积凭证。形象的理解,线程阻塞需要消耗凭证(permit),这个凭证最多只有1个。当调用park方法时,如果有凭证,则会直接消耗掉这个凭证然后正常退出,如果无凭证,就必须阻塞等待凭证可用。而unpark则相反,它会增加一个凭证,但凭证最多只能有1个,累加无效。

posted on 2021-03-18 11:51  whn051799  阅读(43)  评论(0编辑  收藏  举报