【并发编程】LockSupport源码详解

目录

一、前言

1.1 简介

1.2 为什么说LockSupport是Java并发的基石?

二、LockSupport的用途

2.1 LockSupport的主要方法

2.2 使用案例

2.3 总结

三、LockSupport 源码分析

3.1 学习原理前的前置知识

3.1.1 Unsafe.park()和Unsafe.unpark()

3.1.2wait和notify/notifyAll

3.1.3 LockSupport灵活性

3.2 LockSupport中的主要成员及其加载时的初始化

3.2.1 parkBlockerOffset

3.2.2 SEED, PROBE, SECONDARY

3.3 构造方法

3.4 park方法

3.5 parkNanos 方法

3.6 parkUntil 方法

3.7 unpark 方法

3.8 LockSupport原理总结

四、中断响应


一、前言

1.1 简介

LockSupport是concurrent包中的一个线程阻塞工具类,所有的方法都是静态方法,不提供构造,可以让线程在任意位置阻塞,当然阻塞之后肯定得有唤醒的方法。

LockSupport用来创建锁和其他同步类的基本线程阻塞原语。简而言之,当调用 LockSupport.park()时,表示当前线程将会等待,直至获得许可,当调用 LockSupport.unpark()时,必须把等待获得许可的线程作为参数进行传递,好让此线程继续运行。

1.2 为什么说LockSupportJava并发的基石?

当需要阻塞或唤醒一个线程的时候,JVM都会使用LockSupport工具类来完成相应工作。LockSupport定义了一组的公共静态方法,这些方法提供了最基本的线程阻塞和唤醒功能,而LockSupport也被称为构建同步组件的基础工具

Java并发组件和并发工具类如下:

  • 并发组件:线程池、阻塞队列、Future和FutureTask、Lock和Condition。
  • 并发工具:CountDownLatch、CyclicBarrier、Semaphore和Exchanger。

并发组件和并发工具大都是基于AQS来实现的:

队列同步器AbstractQueuedSynchronizer(以下简称同步器),是用来构建锁或者其他同步组件的基础框架,它使用了一个int成员变量表示同步状态,通过内置的FIFO队列来完成资源获取线程的排队工作,并发包的作者(Doug Lea)期望它能够成为实现大部分同步需求的基础。

而AQS中的控制线程又是通过LockSupport类来实现的,因此可以说,LockSupport是Java并发基础组件中的基础组件。LockSupport定义了一组以park开头的方法用来阻塞当前线程,以及unpark(Thread thread)方法来唤醒一个被阻塞的线程。

二、LockSupport的用途

2.1 LockSupport的主要方法

接下面我来看看LockSupport有哪些常用的方法。主要有两类方法:park(阻塞线程)和unpark(解除阻塞)。

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); // 获取线程的Blocker对象

为什么叫park呢,park英文意思为停车。我们如果把Thread看成一辆车的话,park就是让车停下,unpark就是让车启动然后跑起来。

2.2 使用案例

我们写一个例子来看看这个工具类怎么用的。

public class LockSupportDemo {
    public static Object u = new Object();
    static ChangeObjectThread t1 = new ChangeObjectThread("t1");
    static ChangeObjectThread t2 = new ChangeObjectThread("t2");
    public static class ChangeObjectThread extends Thread {
        public ChangeObjectThread(String name) {
            super(name);
        }
        @Override public void run() {
            synchronized (u) {
                System.out.println("in " + getName());
                LockSupport.park();
                if (Thread.currentThread().isInterrupted()) {
                    System.out.println("被中断了");
                }
                System.out.println("继续执行");
            }
        }
    }
    public static void main(String[] args) throws InterruptedException {
        t1.start();
        Thread.sleep(1000L);
        t2.start();
        Thread.sleep(3000L);
        t1.interrupt();
        LockSupport.unpark(t2);
        t1.join();
        t2.join();
    }
}

运行的结果如下:

这儿park和unpark其实实现了wait和notify的功能,不过还是有一些差别的。

  1. park不需要获取某个对象的锁。
  2. 因为中断的时候park不会抛出InterruptedException异常,所以需要在park之后自行判断中断状态,然后做额外的处理。

我们再来看看Object blocker对象,这是个什么东西呢?这其实就是方便在线程dump的时候看到具体的阻塞对象的信息。

"t1" #10 prio=5 os_prio=31 tid=0x00007f95030cc800 nid=0x4e03 waiting on condition [0x00007000011c9000]
   java.lang.Thread.State: WAITING (parking)
    at sun.misc.Unsafe.park(Native Method)
    at java.util.concurrent.locks.LockSupport.park(LockSupport.java:304)
    // `下面的这个信息`
    at com.wtuoblist.beyond.concurrent.demo.chapter3.LockSupportDemo$ChangeObjectThread.run(LockSupportDemo.java:23) // 
    - locked <0x0000000795830950> (a java.lang.Object)

 blocker对象通过LockSupport.getBlocker方法获得。blocker对象只有在线程阻塞的时候才会被赋值,blocker对象是Thread线程类中的成员属性。

还有一个地方需要注意,相对于线程的stop和resume,park和unpark的先后顺序并不是那么严格。stop和resume如果顺序反了,会出现死锁现象。而park和unpark却不会。这又是为什么呢?还是看一个例子

public class LockSupportDemo {
    public static Object u = new Object();
    static ChangeObjectThread t1 = new ChangeObjectThread("t1");
    public static class ChangeObjectThread extends Thread {
        public ChangeObjectThread(String name) {
            super(name);
        }
        @Override public void run() {
            synchronized (u) {
                System.out.println("in " + getName());
                try {
                    Thread.sleep(1000L);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                LockSupport.park();
                if (Thread.currentThread().isInterrupted()) {
                    System.out.println("被中断了");
                }
                System.out.println("继续执行");
            }
        }
    }
    public static void main(String[] args) {
        t1.start();
        LockSupport.unpark(t1);
        System.out.println("unpark invoked");
    }
}

t1内部有休眠1s的操作,所以unpark肯定先于park的调用,但是t1最终仍然可以完结。这是因为park和unpark会对每个线程维持一个许可(boolean值)

  1. unpark调用时,如果当前线程还未进入park,则许可为true,并且不会去执行unpark。
  2. park调用时,判断许可是否为true,如果是true,则继续往下执行;如果是false,则等待,直到许可为true。

意思就是如果新执行unpark,如果发现当前线程还没有执行park呢,那么unpark就会停在那等待,等到真的去执行了park之后,才会继续向下执行unpark。这个功能是通过两个方法共同维护的一个boolean类型的许可变量实现的。

我们再看看jdk的文档描述

【并发编程】LockSupport源码详解_第1张图片

【并发编程】LockSupport源码详解_第2张图片

2.3 总结

  • park和unpark可以实现类似wait和notify的功能,但是并不和wait和notify交叉,也就是说unpark不会对wait起作用,notify也不会对park起作用。
  • park和unpark的使用不会出现死锁的情况.
  • blocker的作用是在dump线程的时候看到阻塞对象的信息。

三、LockSupport 源码分析

3.1 学习原理前的前置知识

3.1.1 Unsafe.park()Unsafe.unpark()

在分析 LockSupport函数之前,先引入 sun.misc.Unsafe类中的 park和 unpark函数,因为 LockSupport的核心函数都是基于Unsafe类中定义的 park和 unpark函数,下面给出两个函数的定义:

public native void park(boolean isAbsolute, long time);
public native void unpark(Thread thread);

 对两个函数的说明如下:

  • Unsafe.park函数:阻塞线程,并且该线程在下列情况发生之前都会被阻塞:① 调用 unpark函数,释放该线程的许可之前。② 该线程被中断之前。③ 设置的时间到之前。并且,当 time为绝对时间时,isAbsolute为 true,否则,isAbsolute为 false。当time为0时,表示无限等待,直到 unpark发生。
  • Unsafe.unpark函数:释放线程的许可,激活调用 park后阻塞的线程。该函数不是安全的,调用该函数时要确保线程依旧存活。

3.1.2waitnotify/notifyAll

在看park()和unpark()之前,不妨来看下在没有LockSupport之前,是怎么实现让线程等待/唤醒的。

在没有LockSupport之前,线程的挂起和唤醒都是通过Object的wait和notify/notifyAll方法实现。

写一段例子代码,线程A执行一段业务逻辑后调用wait阻塞住自己。主线程调用notify方法唤醒线程A,线程A然后打印自己执行的结果。

public static void main(String[] args) throws Exception {
    final Object obj = new Object();
    Thread A = new Thread(() -> {
        int sum = 0;
        for (int i = 0; i < 10; i++) {
            sum += i;
        }
        try {
            obj.wait();
        } catch (Exception e) {
            e.printStackTrace();
        }
        System.out.println(sum);
    });
    A.start();
    //睡眠一秒钟,保证线程A已经计算完成,阻塞在wait方法
    Thread.sleep(1000);
    obj.notify();
}

执行这段代码,不难发现这个错误:

【并发编程】LockSupport源码详解_第3张图片

原因很简单,wait和notify/notifyAll方法只能在同步代码块里用(这个有的面试官也会考察)。所以将代码修改为如下就可正常运行了:

public static void main(String[] args) throws Exception {
    final Object obj = new Object();
    Thread A = new Thread(() -> {
        int sum = 0;
        for (int i = 0; i < 10; i++) {
            sum += i;
        }
        try {
            synchronized (obj) {
                obj.wait();
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
        System.out.println(sum);
    });
    A.start();
    // 睡眠一秒钟,保证线程A已经计算完成,阻塞在wait方法
    Thread.sleep(1000);
    synchronized (obj) {
        obj.notify();
    }
}

那如果咱们换成LockSupport呢?简单得很,看代码:

public static void main(String[] args) throws Exception {
    Thread A = new Thread(() -> {
        int sum = 0;
        for (int i = 0; i < 10; i++) {
            sum += i;
        }
        LockSupport.park();
        System.out.println(sum);
    });
    A.start();
    // 睡眠一秒钟,保证线程A已经计算完成,阻塞在wait方法
    Thread.sleep(1000);
    LockSupport.unpark(A);
}

3.1.3 LockSupport灵活性

通过上面的例子,我们就能明白LockSupport类就是为了提供与wait和notify/notifyAll方法相同的功能,并且使用起来更加简单方便而创造的工具类。

如果只是LockSupport在使用起来比Object的wait/notify简单,那还真没必要专门讲解下LockSupport。最主要的是灵活性。

上边的例子代码中,主线程调用了Thread.sleep(1000)方法来等待线程A计算完成进入wait状态。如果去掉Thread.sleep()调用:

public static void main(String[] args) throws Exception {
    final Object obj = new Object();
    Thread A = new Thread(() -> {
        int sum = 0;
        for (int i = 0; i < 10; i++) {
            sum += i;
        }
        try {
            synchronized (obj) {
                obj.wait();
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
        System.out.println(sum);
    });
    A.start();
    // 睡眠一秒钟,保证线程A已经计算完成,阻塞在wait方法
    //Thread.sleep(1000);
    synchronized (obj) {
        obj.notify();
    }
}

多次执行后,我们会发现:有的时候能够正常打印结果并退出程序,但有的时候线程无法打印结果阻塞住了。原因就在于主线程先调用完notify后,线程A才进入执行wait方法,导致线程A一直阻塞住。由于线程A不是后台线程,所以整个程序无法退出。

那如果换做LockSupport呢?LockSupport就支持主线程先调用unpark后,线程A再调用park而不被阻塞吗?是的,没错。代码如下:

public static void main(String[] args) throws Exception {
    Thread A = new Thread(() -> {
        int sum = 0;
        for (int i = 0; i < 10; i++) {
            sum += i;
        }
        LockSupport.park();
        System.out.println(sum);
    });
    A.start();
    // 睡眠一秒钟,保证线程A已经计算完成,阻塞在wait方法
    //Thread.sleep(1000);
    LockSupport.unpark(A);
}

不管你执行多少次,这段代码都能正常打印结果并退出。这就是LockSupport最大的灵活所在。

同样的park()和unpark()也不会遇到Thread.suspend 和 Thread.resume所可能引发的死锁问题。

小结一下,LockSupport比Object的wait/notify有两大优势:

  1. LockSupport不需要在同步代码块里 。所以线程间也不需要维护一个共享的同步对象了,实现了线程间的解耦。
  2. unpark函数可以先于park调用,所以不需要担心线程间的执行的先后顺序。

3.2 LockSupport中的主要成员及其加载时的初始化

public class LockSupport {
    // Hotspot implementation via intrinsics API
    // UNSAFE字段表示 sun.misc.Unsafe类
    // 一般程序中不允许直接调用
    private static final sun.misc.Unsafe UNSAFE;
    // 而 long型的表示Thread实例对象相应字段在内存中的偏移地址,可以通过该偏移地址获取或者设置该字段的值。
    // 表示Thread类中的parkBlocker对象的内存偏移地址
    private static final long parkBlockerOffset;
    // 表示Thread类中的threadLocalRandomSeed对象的内存偏移地址
    private static final long SEED;
    // 表示Thread类中的threadLocalRandomProbe对象的内存偏移地址
    private static final long PROBE;
    // 表示Thread类中的threadLocalRandomSecondarySeed对象的内存偏移地址
    private static final long SECONDARY;
    // 静态代码块,会在加载时自动执行
    static {
        try {
            // 获取Unsafe实例
            UNSAFE = sun.misc.Unsafe.getUnsafe();
            // 线程类类型
            Class tk = Thread.class;
            // 获取Thread的parkBlocker字段的内存偏移地址
            parkBlockerOffset = UNSAFE.objectFieldOffset
                (tk.getDeclaredField("parkBlocker"));
            // 获取Thread的threadLocalRandomSeed字段的内存偏移地址
            SEED = UNSAFE.objectFieldOffset
                (tk.getDeclaredField("threadLocalRandomSeed"));
            // 获取Thread的threadLocalRandomProbe字段的内存偏移地址
            PROBE = UNSAFE.objectFieldOffset
                (tk.getDeclaredField("threadLocalRandomProbe"));
            // 获取Thread的threadLocalRandomSecondarySeed字段的内存偏移地址
            SECONDARY = UNSAFE.objectFieldOffset
                (tk.getDeclaredField("threadLocalRandomSecondarySeed"));
        } catch (Exception ex) { throw new Error(ex); }
    }
}

不难发现,他们在初始化的时候都是通过Unsafe去获得他们的内存地址。

下面讲一下这几个成员属性。

3.2.1 parkBlockerOffset

表示Thread类中的parkBlocker对象的内存偏移地址,提供给setBlocker和getBlocker使用。

private static void setBlocker(Thread t, Object arg) {
    // Even though volatile, hotspot doesn't need a write barrier here.
    UNSAFE.putObject(t, parkBlockerOffset, arg);
}

public static Object getBlocker(Thread t) {
    if (t == null)
        throw new NullPointerException();
    return UNSAFE.getObjectVolatile(t, parkBlockerOffset);
}

上面方法中的参数t是Thread线程对象,parkBlocker对象就是Thread类的成员属性

// Thread类的成员属性
volatile Object parkBlocker;

上面的setBlocker和getBlocker方法,就是利用偏移地址parkBlockerOffset操作Thread对象中的parkBlocker。

由于Unsafe.putObject是无视Java访问限制,直接修改目标内存地址的值。即使对象被volatile修饰,也是不需要写屏障的。

这边的偏移量就算Thread这个类里面变量parkBlocker在内存中的偏移量:

JVM的实现可以自由选择如何实现Java对象的“布局“,也就是在内存里Java对象的各个部分放在哪里,包括对象的实例字段和一些元数据之类。 sun.misc.Unsafe里关于对象字段访问的方法把对象布局抽象出来,它提供了objectFieldOffset()方法用于获取某个字段相对 Java对象的“起始地址”的偏移量,也提供了getInt、getLong、getObject之类的方法可以使用前面获取的偏移量来访问某个Java 对象的某个字段。

为什么要用偏移量来获取对象?干吗不要直接写个get、set方法?

parkBlocker就是在线程处于阻塞的情况下才被赋值。线程都已经被阻塞了,如果不通过这种内存的方法,而是直接调用线程内的方法,线程是不会回应调用的。

3.2.2 SEED, PROBE, SECONDARY

LockSupport中的这三个成员属性,就是下面这三个Thread类中的成员属性相对应Thread对象的偏移地址。

@sun.misc.Contended("tlr")
long threadLocalRandomSeed;
/** Probe hash value; nonzero if threadLocalRandomSeed initialized */
@sun.misc.Contended("tlr")
int threadLocalRandomProbe;
/** Secondary seed isolated from public ThreadLocalRandom sequence */
@sun.misc.Contended("tlr")
int threadLocalRandomSecondarySeed;

都是Thread类中的内存偏移地址,主要用于ThreadLocalRandom类进行随机数生成,它要比Random性能好很多,可以看jdk源码ThreadLocalRandom.java了解详情,这儿就不贴了。

3.3 构造方法

LockSupport 只有一个私有构造函数,无法被实例化。

// 私有构造函数,无法被实例化
private LockSupport() {}

因为LockSupport中定义的都是static静态方法,所以在使用LockSupport时并不需要实例化出一个对象,直接调用类的静态方法即可。

下面我们分析一下LockSupport最常用的几个方法的源码。

3.4 park方法

park 函数有两个重载版本,方法摘要如下:

public static void park();
public static void park(Object blocker);

两个函数的区别在于 park()函数有没有 blocker,即没有设置线程的 parkBlocker字段。

park(Object)型函数如下:

public static void park(Object blocker) {
    // 获取当前线程
    Thread t = Thread.currentThread();
    // 设置Blocker
    setBlocker(t, blocker);
    // 获取许可
    UNSAFE.park(false, 0L);
    // 重新可运行后再此设置Blocker
    setBlocker(t, null);
}

调用 park函数时,首先获取当前线程,然后设置当前线程的 parkBlocker字段,即调用 setBlocker函数,之后调用 Unsafe类的park函数,之后再调用 setBlocker函数。那么问题来了,为什么要在此 park函数中调用两次 setBlocker函数呢? 

原因其实很简单,调用 park函数时,当前线程首先设置好 parkBlocker字段,然后再调用 Unsafe的 park函数,此后,当前线程就已经阻塞了,等待该线程的 unpark函数被调用,所以后面的一个 setBlocker函数无法运行,unpark函数被调用,该线程获得许可后,就可以继续运行了,也就运行第二个 setBlocker,把该线程的 parkBlocker字段设置为null,这样就完成了整个 park函数的逻辑。如果没有第二个 setBlocker,那么之后没有调用 park(Object blocker),而直接调用 getBlocker函数,得到的还是前一个 park(Object blocker)设置的 blocker,显然是不符合逻辑的。总之,必须要保证在 park(Object blocker)整个函数执行完后,该线程的parkBlocker字段又恢复为 null。所以,park(Object)型函数里必须要调用 setBlocker函数两次。

setBlocker方法如下:此方法用于设置线程t 的 parkBlocker字段的值为 arg。

private static void setBlocker(Thread t, Object arg) {
    // 设置线程t的parkBlocker字段的值为arg
    UNSAFE.putObject(t, parkBlockerOffset, arg);
}

另外一个无参重载版本,park()函数如下。

public static void park() {
    // 获取许可,设置时间为无限长,直到可以获取许可
    UNSAFE.park(false, 0L);
}

调用了park函数后,会禁用当前线程,除非许可可用。在以下三种情况之一发生之前,当前线程都将处于休眠状态,即下列情况发生时,当前线程会获取许可,可以继续运行。

  1. 其他某个线程将当前线程作为目标调用 unpark;
  2. 其他某个线程中断当前线程;
  3. 该调用不合逻辑地(即毫无理由地)返回;

3.5 parkNanos 方法

此函数表示在许可可用前禁用当前线程,并最多等待指定的等待时间。具体函数如下。该函数也是调用了两次 setBlocker函数,nanos参数表示相对时间,表示等待多长时间。

public static void park() {
    // 获取许可,设置时间为无限长,直到可以获取许可
    UNSAFE.park(false, 0L);
}

3.6 parkUntil 方法

此函数表示在指定的时限前禁用当前线程,除非许可可用,具体函数如下:该函数也调用了两次 setBlocker函数,deadline参数表示绝对时间,表示指定的时间。

public static void parkUntil(Object blocker, long deadline) {
    // 获取当前线程
    Thread t = Thread.currentThread();
    // 设置Blocker
    setBlocker(t, blocker);
    UNSAFE.park(true, deadline);
    // 设置Blocker为null
    setBlocker(t, null);
}

3.7 unpark 方法

此函数表示如果给定线程的许可尚不可用,则使其可用。如果线程在 park 上受阻塞,则它将解除其阻塞状态。否则,保证下一次调用 park 不会受阻塞。如果给定线程尚未启动,则无法保证此操作有任何效果。具体函数如下:释放许可,指定线程可以继续运行。

public static void unpark(Thread thread) {
    if (thread != null) // 线程为不空
        UNSAFE.unpark(thread); // 释放该线程许可
}

3.8 LockSupport原理总结

通过学习上面几个方法的源码,我们就发现LockSupport的底层实现都是基于Unsafe.park()和Unsafe.unpark()。

Unsafe源码也相对简单,看下就行了:

void
sun::misc::Unsafe::unpark (::java::lang::Thread *thread)
{
  natThread *nt = (natThread *) thread->data;
  nt->park_helper.unpark ();
}
 
void
sun::misc::Unsafe::park (jboolean isAbsolute, jlong time)
{
  using namespace ::java::lang;
  Thread *thread = Thread::currentThread();
  natThread *nt = (natThread *) thread->data;
  nt->park_helper.park (isAbsolute, time);
}

总之使用park和unpark进行线程的阻塞和唤醒操作,LockSuport.park和LockSuport.unpark是基于Unsafe类中的park()和unpark()方法来实现的,而再往底层看,Unsafe又是借助系统层(C语言)方法pthread_cond_wait和pthread_cond_signal来操作pthread_u和pthread_cond实现的,通过pthread_cond_wait函数可以对一个线程进行阻塞操作,在这之前,必须先获取pthread_mutex,通过pthread_cond_signal函数对一个线程进行唤醒操作。

pthread_mutex和pthread_cond使用示例如下:

void *r1(void *arg)
{
    pthread_mutex_t* mutex = (pthread_mutex_t *)arg;
    static int cnt = 10;
    while(cnt--)
    {
        printf("r1: I am wait.\n");
        pthread_mutex_lock(mutex);
        /* mutex参数用来保护条件变量的互斥锁,调用pthread_cond_wait前mutex必须加锁 */
        pthread_cond_wait(&cond, mutex); 
        pthread_mutex_unlock(mutex);
    }
    return "r1 over";
}

void *r2(void *arg)
{
    pthread_mutex_t* mutex = (pthread_mutex_t *)arg;
    static int cnt = 10;
    while(cnt--)
    {
        pthread_mutex_lock(mutex);
        printf("r2: I am send the cond signal.\n");
        pthread_cond_signal(&cond);
        pthread_mutex_unlock(mutex);
        sleep(1);
    }
    return "r2 over";
}

注意,Linux下使用pthread_cond_signal的时候,会产生“惊群”问题的,但是Java中是不会存在这个“惊群”问题的,那么Java是如何处理的呢?

实际上,Java只会对一个线程调用pthread_cond_signal操作,这样肯定只会唤醒一个线程,也就不存在所谓的惊群问题。Java在语言层面实现了自己的线程管理机制(阻塞、唤醒、排队等),每个Thread实例都有一个独立的pthread_u和pthread_cond(系统层面的/C语言层面),在Java语言层面上对单个线程进行独立唤醒操作。(Java中线程只能在Java线程库的指挥下作战,无法直接获取同一个pthread_mutex或者pthread_cond。Java这种实现线程机制的实现实在太巧妙了,虽然底层都是使用pthread_mutex和pthread_cond这些方法,但是貌似C/C++还没这么强大易用的线程库)

具体LockSuuport.park和LockSuuport.unpark的底层实现可以参考对应JDK源码,下面看一下gdb打印处于LockSuuport.park时的线程状态信息:

【并发编程】LockSupport源码详解_第4张图片

由上图可知底层确实是基于pthread_cond函数来实现的。

我们在使用LockSupport过程中,多次调用unpark方法和调用一次unpark方法效果一样,因为都是直接将_counter赋值为1,而不是加1。简单说就是:线程A连续调用两次LockSupport.unpark(B)方法唤醒线程B,然后线程B调用两次LockSupport.park()方法, 线程B依旧会被阻塞。因为两次unpark调用效果跟一次调用一样,只能让线程B的第一次调用park方法不被阻塞,第二次调用依旧会阻塞。

四、中断响应

import java.util.concurrent.locks.LockSupport;
class MyThread extends Thread {
    private Object object;
    public MyThread(Object object) {
        this.object = object;
    }
    public void run() {
        System.out.println("before interrupt");
        try {
            // 休眠3s  为了让主线程先执行park()
            Thread.sleep(3000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        Thread thread = (Thread) object;
        // 3、执行完park之后,执行中断线程
        thread.interrupt();
        System.out.println("after interrupt");
    }
}
public class InterruptDemo {
    public static void main(String[] args) {
        MyThread myThread = new MyThread(Thread.currentThread());
        // 1、执行线程
        myThread.start();
        System.out.println("before park");
        // 2、执行park,获取许可
        LockSupport.park("ParkAndUnparkDemo");
        // 4、线程被成功唤醒了
        System.out.println("after park");
    }
}

运行结果:

before park
before interrupt
after interrupt
after park

可以看到,在主线程调用 park阻塞后,在 myThread线程中发出了中断信号,此时主线程会继续运行,也就是说明此时 interrupt起到的作用与 unpark一样。

总之,线程使用LockSupport.park方法被阻塞后,然后被interrupt()方法唤醒之后,该线程就再也不会被 LockSupport.park方法阻塞了,会被直接唤醒。但是被interrupt()方法唤醒之后,该线程仍然可以再次被LockSupport.park方法阻塞。


参考文章:https://www.cnblogs.com/zhengzhaoxiang/p/13973980.html

你可能感兴趣的:(#,并发编程,Java,Java,多线程,阻塞,并发,LockSupport)