LockSupport用法

# 概述

此类与使用它的每个线程关联一个许可(与Semaphore类似),用于创建锁和其他同步类的基本线程阻塞原语。

如果许可证可用, 调用park方法将立即返回,并在此过程中消耗许可证,否则可能会阻塞。如果许可证尚不可用,则调用unpark可使许可证可用。 (但与信号量不同,许可证不会累积。最多有一个。)

也就是说如果先调用unpark,再调用一次park方法不会阻塞。unpark方法调用多次,许可证只有一个,第二次调用park方法时任然会阻塞。

park方法也可以在任何其他时间“无缘无故”地返回,因此通常必须在循环中调用,该循环在返回时重新检查条件。
从这个意义上说, park是“忙等待”的优化,不会浪费太多时间,但必须与unpark搭配使用。

# 类属性

public class LockSupport {
    private static final sun.misc.Unsafe UNSAFE;
    private static final long parkBlockerOffset;
    private static final long SEED;
    private static final long PROBE;
    private static final long SECONDARY;
    static {
        try {
            //获取UnSafe类实例
            UNSAFE = sun.misc.Unsafe.getUnsafe();
            Class<?> tk = Thread.class;
            //获取parkBlocker字段在Thread对象中的内存偏移量
            parkBlockerOffset = UNSAFE.objectFieldOffset
                (tk.getDeclaredField("parkBlocker"));
            SEED = UNSAFE.objectFieldOffset
                (tk.getDeclaredField("threadLocalRandomSeed"));
            PROBE = UNSAFE.objectFieldOffset
                (tk.getDeclaredField("threadLocalRandomProbe"));
            SECONDARY = UNSAFE.objectFieldOffset
                (tk.getDeclaredField("threadLocalRandomSecondarySeed"));
        } catch (Exception ex) { throw new Error(ex); }
    }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23

类加载时获取Unsafe实例,获取Thread

# 构造函数

private LockSupport() {} // Cannot be instantiated.
1

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

# 核心函数

主要使用sun.misc.UnSafe类中的park``unPark方法进行阻塞和唤醒

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

public native void unpark(Thread thread);
1
2
3

park函数阻塞线程,等待许可可用。线程将会休眠直到发生以下情况:

  1. 其他线程调用unpark唤醒该线程

  2. 其他线程中断当前线程

  3. 达到指定的等待时间

  4. 调用虚假地(即无缘无故地)返回

由于第四点的存在,所以park需要在循环中使用,在唤醒后检查条件,未满足条件再次调用park。当time为绝对时间时,isAbsolutetrue,否则,isAbsolutefalse。当time为0时,表示无限等待,直到unpark发生。

unpark函数使给定线程的许可可用(如果它尚不可用)。如果线程在park上被阻塞,那么它将解除阻塞。否则,保证其下一次调用park不会阻塞。如果给定线程尚未启动,则不能保证此操作有任何效果

# park

public static void park() {
    UNSAFE.park(false, 0L);
}
1
2
3
public static void park(Object blocker) {
    Thread t = Thread.currentThread();
    setBlocker(t, blocker);
    UNSAFE.park(false, 0L);
    setBlocker(t, null);
}
private static void setBlocker(Thread t, Object arg) {
    // Even though volatile, hotspot doesn't need a write barrier here.
    UNSAFE.putObject(t, parkBlockerOffset, arg);
}
1
2
3
4
5
6
7
8
9
10

两个方法的主要区别在于设置bloker。线程被唤醒后主动清除bloker,以免影响在之后的park中被认为是新设置的broker

# parkNanos

public static void parkNanos(Object blocker, long nanos) {
    if (nanos > 0) {
        Thread t = Thread.currentThread();
        setBlocker(t, blocker);
        UNSAFE.park(false, nanos);
        setBlocker(t, null);
    }
}
1
2
3
4
5
6
7
8

支持设置阻塞毫秒数

# parkUntil

public static void parkUntil(Object blocker, long deadline) {
    Thread t = Thread.currentThread();
    setBlocker(t, blocker);
    UNSAFE.park(true, deadline);
    setBlocker(t, null);
}
1
2
3
4
5
6

支持设置阻塞绝对时间。

# unpark

public static void unpark(Thread thread) {
    if (thread != null)
        UNSAFE.unpark(thread);
}
1
2
3
4

使给定线程的许可可用(如果它尚不可用)。如果线程在park上被阻塞,那么它将解除阻塞。否则,保证其下一次调用park不会阻塞。如果给定线程尚未启动,则不能保证此操作有任何效果。

# getBlocker

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

获取该线程最近一次park时设置的阻塞对象,如果线程已被唤醒,则返回null。这是一个瞬时快照,获取阻塞对象快照后,线程可能已被唤醒,或者被设置了另一个阻塞对象。

# 示例用法

这是一个先进先出不可重入锁:

class FIFOMutex {
    private final AtomicBoolean locked = new AtomicBoolean(false);
    private final Queue<Thread> waiters
            = new ConcurrentLinkedQueue<Thread>();

    public void lock() {
        boolean wasInterrupted = false;
        Thread current = Thread.currentThread();
        waiters.add(current);

        // Block while not first in queue or cannot acquire lock
        while (waiters.peek() != current ||
                !locked.compareAndSet(false, true)) {
            LockSupport.park(this);
            if (Thread.interrupted()) // ignore interrupts while waiting
                wasInterrupted = true;
        }

        waiters.remove();
        if (wasInterrupted)          // reassert interrupt status on exit
            current.interrupt();
    }

    public void unlock() {
        locked.set(false);
        LockSupport.unpark(waiters.peek());
    }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28

需要注意的是park方法并不会释放锁,使用不当,也可能造成死锁。

public static void main(String[] args){
    try {
        Thread t0 = new Thread() {
            public void run() {
                synchronized (this) {
                    System.out.println("线程t0 调用park");
                    LockSupport.park();
                }
            }
        };
        t0.start();
        Thread.sleep(2*1000);
        System.out.println("main线程准备调用unpark");
        synchronized (t0) {
            LockSupport.unpark(t0);
            System.out.println("main线程调用unpark");
        }
    }catch (Throwable t) {
        System.out.println("Caught in main: " + t);
        t.printStackTrace();
    }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22

由于t0线程调用park方法并未释放在对象t0上的锁,main线程获取t0对象上的锁时被阻塞,导致死锁。

上次更新: 2023/04/09, 16:34:32
最近更新
01
go-admin-ui项目仿写练手1-登录页
06-29
02
maven依赖问题
06-17
03
JVM相关命令
02-21
更多文章>