LockSupport是用来创建锁和其他同步类的基本线程阻塞原语。

LockSupport中park()和unpark()方法的作用分别是阻塞线程和解除阻塞线程。

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

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

  1. 使用Object中的wait()方法让线程等待,使用Object中的notify()方法唤醒线程。
  2. 使用JUC包中的Condition的await()方法让线程等待,使用signal()方法唤醒线程。
  3. LockSupport类可以阻塞当前线程已经唤醒其指定被阻塞的线程。

使用Object中的wait和notify方法需要注意:

  • Object类的wait、notify、notifyAll用于线程等待和唤醒的方法,都必须在synchronized内部执行,必须用到关键字synchronized。
  • 先wait后notify、notifyAll方法,等待中的线程才会被唤醒,否则无法唤醒。

传统的synchronized和Lock实现等待唤醒通知的约束

  • 线程先要获得并持有锁,必须在锁块(synchronized和Lock)中
  • 必须要先等待后唤醒,线程才能够被唤醒

LockSupport类中的park等待和unpark唤醒

通过park()和unpark(thread)方法来实现阻塞和唤醒线程的操作。

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

Permit只有两个值1和0,默认为0。可以把许可看成是一种(0,1)信号量(Semaphore),但与Semaphore不同的是,许可的累加上限是1。

public static void park(Object blocker); // 暂停当前线程
public static void parkNanos(Object blocker, long nanos); // 暂停当前线程,不过有超时时间的限制
public static void parkUntil(Object blocker, long deadline); // 暂停当前线程,直到某个时间
public static void park(); // 无期限暂停当前线程
public static void parkNanos(long nanos); // 暂停当前线程,不过有超时时间的限制
public static void parkUntil(long deadline); // 暂停当前线程,直到某个时间
public static void unpark(Thread thread); // 恢复当前线程
public static Object getBlocker(Thread t);

阻塞:

park()/park(Object blocker)

阻塞当前线程/阻塞传入的具体线程

//调用LockSupport.park时的底层方法
public static void park() {
    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()方法会立即返回。

public class LockSupportDemo {
    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 {
            TimeUnit.SECONDS.sleep(2);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }

        Thread t2 = new Thread(() -> {
            System.out.println(Thread.currentThread().getName()+"\t"+"调用");
            LockSupport.unpark(t1);
            System.out.println(Thread.currentThread().getName()+"\t"+"二次调用");
        }, "t2");
        t2.start();
    }
}

结果:

 线程阻塞需要消耗凭证(permit),这个凭证最多只有一个

当调用park方法时,如果有凭证,则会直接消耗掉这个凭证然后正常退出;如果无凭证,就必须阻塞线程等待凭证可用。

而unpark方法则相反,它会增加一个凭证,但凭证最多只能有一个,无法累加。

 

 posted on 2020-12-19 13:55  会飞的金鱼  阅读(96)  评论(0)    收藏  举报