前言

LockSupport是rt.jar下的工具类,它的作用是挂起和唤醒线程,它在JUC很多同步组件中都会用到,比方AQS。LockSupport类与每个应用它的线程都会关联一个许可证,在默认状况下调用LockSupport类办法的线程是没有许可证的。LockSupport外部是用Unsafe的park和unpark办法实现的。

次要办法

(1)void park()

调用park()办法会阻塞以后线程,除非以后线程曾经拿到了与LockSupport关联的许可证。当其余线程调用unpark(Thread thread)办法并且将以后线程作为参数时,或者调用以后线程的interrupt()办法时,以后线程会从阻塞状态返回。然而,须要留神的是,与调用sleep(),wait()等办法不同,调用park()办法被中断返回时,不会抛出InterruptedException异样。还有须要留神的是,park()办法返回时不会通知你是什么起因返回(unpark或interrupt),但咱们能够依据调用前后的中断标记是否扭转来判断是否因为interrupt返回的。

(2)void unpark(Thread thread)

线程调用unpark办法时,如果参数线程thread没有持有许可证,则让其持有,如果thread在此前因调用park而被阻塞,则调用unpark后,该线程会被唤醒。如果thread之前没有调用park,则调用unpark后,再调用park,线程会立刻返回,而不会阻塞挂起。用代码来阐明:

import java.util.concurrent.locks.LockSupport;public class UnparkDemo {    public static void main(String[] args) {        System.out.println("give current thread a permit");        LockSupport.unpark(Thread.currentThread());        System.out.println("current thread begin park");        LockSupport.park();        System.out.println("current thread end park");    }}

输入后果如下:
以后线程调用park办法后并没有阻塞挂起,而是立刻返回了,因为此前调用unpark给了它许可证。

(3)void parkNanos(long nanos)

和park办法相似,但有个超时参数,如果期待了nanos(单位纳秒)工夫后,以后线程没有被唤醒,会主动返回。

(4)void park(Object blocker)

以后线程调用park(blocker)被挂起时,blocker对象会被记录在以后线程外部,它的作用是当咱们应用诊断工具(如jstack等)dump线程时,能够察看到线程被阻塞在哪个类下面。诊断工具是通过getBlocker()拿到blocker对象的。因而JDK举荐咱们应用带有blocker参数的park办法,例如LockSupport.park(this)。同样用代码来阐明下:

import java.util.concurrent.locks.LockSupport;public class ParkWithBlockerDemo {    public void parkWithBlocker() {        LockSupport.park(this);    }    public static void main(String[] args) {        ParkWithBlockerDemo demo = new ParkWithBlockerDemo();        demo.parkWithBlocker();    }}

程序运行后,线程会被阻塞挂起,此时先用jps命令找到它的pid,而后用jstack pid执行,会有如下后果:

不加blocker参数的话,是不会有这个标记的,感兴趣的同学能够去试试。
park(blocker)办法源码:

public static void park(Object blocker) {    Thread t = Thread.currentThread();    setBlocker(t, blocker);    UNSAFE.park(false, 0L);    setBlocker(t, null);}

代码逻辑很清晰明了,原理就是真正park之前,将blocker对象绑定在以后Thread外部的某个变量中,等park返回后,解除绑定。其中setBlocker源码如下:

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

其中parkBlockerOffset是Thread类parkBlocker变量的内存偏移量。

private static final long parkBlockerOffset;parkBlockerOffset = UNSAFE.objectFieldOffset    (tk.getDeclaredField("parkBlocker"));

(5)parkUntil(long deadline)

与parkNanos不同的是,deadline是个相对工夫戳,示意到某个工夫点后,以后线程会被唤醒。

结束语

最初再来看个栗子来加深LockSupport的了解。

import java.util.Queue;import java.util.concurrent.ConcurrentLinkedDeque;import java.util.concurrent.atomic.AtomicBoolean;import java.util.concurrent.locks.LockSupport;public class FIFOMutex {    private final AtomicBoolean locked = new AtomicBoolean(false);    private final Queue<Thread> waiters = new ConcurrentLinkedDeque<>();    public void lock() {        boolean wasInterrupted = false;        Thread current = Thread.currentThread();        // 把以后线程放到队列开端        waiters.add(current);        // 只有队列头部的线程能力获取锁        while (waiters.peek() != current || locked.compareAndSet(false, true)) {            LockSupport.park(this);            // 记录是否被中断过,并重置中断标记,这里其实就是疏忽中断            if (Thread.interrupted()) {                wasInterrupted = true;            }        }        // 移除队列头部元素,即胜利获取锁的以后线程        waiters.remove();        // 从新响应之前的中断        if (wasInterrupted) {            current.interrupt();        }    }    public void unlock() {        locked.set(false);        // 唤醒本来处于队列第二,当初处于头部的线程,让它尝试获取锁        LockSupport.unpark(waiters.peek()); }}

这是一个先入先出的锁,也就是只有队列的头部线程能力获取锁,如果以后线程不是头部元素,或者锁曾经被其余线程获取,则调用park办法阻塞本人。

这里的wasInterrupted怎么了解呢?如果park办法是因为被中断而返回的,则疏忽中断,只记录wasInterrupted=true。因为这阐明不是其余线程调用理解锁办法unlock()外面的unpark让它返回的,它还不能获取锁,得持续阻塞。尽管以后线程对中断标记不感兴趣,但不代表其余线程也不敢趣味,所以在胜利获取锁后,从新对本人调用interrupt,复原下中断标记的值。

参考资料:
《Java并发编程之美》