目录
1.2 为什么说LockSupport是Java并发的基石?
3.1.1 Unsafe.park()和Unsafe.unpark()
    
     
      一、前言
     
    
   
    
     
      1.1
     
     
      简介
     
    
   
LockSupport是concurrent包中的一个线程阻塞工具类,所有的方法都是静态方法,不提供构造,可以让线程在任意位置阻塞,当然阻塞之后肯定得有唤醒的方法。
LockSupport用来创建锁和其他同步类的基本线程阻塞原语。简而言之,当调用 LockSupport.park()时,表示当前线程将会等待,直至获得许可,当调用 LockSupport.unpark()时,必须把等待获得许可的线程作为参数进行传递,好让此线程继续运行。
    
     
      1.2
     
     
      为什么说
     
     
      LockSupport
     
     
      是
     
     
      Java
     
     
      并发的基石?
     
    
   
    当需要阻塞或唤醒一个线程的时候,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的功能,不过还是有一些差别的。
- park不需要获取某个对象的锁。
- 因为中断的时候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值)
- unpark调用时,如果当前线程还未进入park,则许可为true,并且不会去执行unpark。
- park调用时,判断许可是否为true,如果是true,则继续往下执行;如果是false,则等待,直到许可为true。
意思就是如果新执行unpark,如果发现当前线程还没有执行park呢,那么unpark就会停在那等待,等到真的去执行了park之后,才会继续向下执行unpark。这个功能是通过两个方法共同维护的一个boolean类型的许可变量实现的。
我们再看看jdk的文档描述
     
   
     
   
    
     
      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.2
     
     
      wait
     
     
      和
     
     
      notify/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();
}
执行这段代码,不难发现这个错误:
     
   
    原因很简单,
    
     
      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有两大优势:
    
   
- LockSupport不需要在同步代码块里 。所以线程间也不需要维护一个共享的同步对象了,实现了线程间的解耦。
- 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函数后,会禁用当前线程,除非许可可用。在以下三种情况之一发生之前,当前线程都将处于休眠状态,即下列情况发生时,当前线程会获取许可,可以继续运行。
- 其他某个线程将当前线程作为目标调用 unpark;
- 其他某个线程中断当前线程;
- 该调用不合逻辑地(即毫无理由地)返回;
    
     
      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时的线程状态信息:
     
   
由上图可知底层确实是基于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
 
