【JUC 并发编程】— LockSupport 源码解析

介绍

源码中是这么说的

Basic thread blocking primitives for creating locks and other synchronization classes.
LockSupport 是创建锁和其他同步组件中阻塞线程的基本原语

比如在 AQS 中的 FIFO 队列中,如果前驱节点的状态为 SIGNAL,那么就阻塞当前节点,用的便是 LockSupport


private final boolean parkAndCheckInterrupt() {
    LockSupport.park(this);
    return Thread.interrupted();
}

许可

LockSupport 与每个使用的线程都会关联一个许可 permit,内部使用 unsafe 类实现。如果许可可用,LockSupport.park() 方法会立即返回,并消费这个许可。LockSupport.unpark() 会使许可可用,但是多次调用 unpark() 并不会增加许可,最多一个。


@Test
public void unparkBeforePark() throws Exception {
    System.out.println("before park...");
    LockSupport.unpark(Thread.currentThread());
    LockSupport.park();
    System.out.println("after park...");
}
    

上面代码先调用 unpark(),当前线程关联的许可可用了,下面的 park() 会立即返回,控制台打印

before park...
after park...

@Test
public void multiUnparkAndPark() throws Exception {
    System.out.println("unpark one...");
    LockSupport.unpark(Thread.currentThread());
    System.out.println("unpark two...");
    LockSupport.unpark(Thread.currentThread());
    LockSupport.park();
    System.out.println("park one...");
    LockSupport.park();
    System.out.println("park two...");
}

这段代码先 unpark 当前两次,但是许可只有一个,下面的第一个 park 会消费这个许可,第二个 park 就没有许可可用了,当前线程便会阻塞在这里。控制台打印如下

unpark one...
unpark two...
park one...

方法

park & unpark


@Test
public void park() {
    System.out.println("before park...");
    LockSupport.park();
    System.out.println("after park...");
}

阻塞当前线程,控制台只会打印 “before park...”

有三种情况 LockSupport.park() 会返回

  • LockSupport.unpark(thread) 被调用,并且参数是当前线程
  • thread.interrupter(),当前线程被中断
  • 虚假唤醒

unpark() 和中断唤醒线程


@Test
public void wakeUpAfterUnpark() throws Exception {
    Thread thread = new Thread(new Runnable() {
        @Override
        public void run() {
            System.out.println("before park...");
            LockSupport.park();
            System.out.println("after park...");
        }
    });
    thread.start();
    Thread.sleep(1000L);

    //System.out.println("main thread unpark");
    //LockSupport.unpark(thread);

    System.out.println("main thread interrupter child thread");
    thread.interrupt();
}

控制台打印

before park...
main thread interrupter child thread
after park...

需要注意的是,park 方法并不会记录是什么原因导致返回,unpark?还是中断?所以调用者应该检查需要 park 返回的条件,比如线程中断才返回,看下面例子


@Test
public void wakeUpAfterInterrupter() throws Exception {
    Thread thread = new Thread(new Runnable() {
        @Override
        public void run() {
            System.out.println("interrupter...");
            while (!Thread.currentThread().isInterrupted()) {
                LockSupport.park();
            }
            System.out.println("after park...");
        }
    });
    thread.start();
    Thread.sleep(1000L);

    System.out.println("main thread interrupter child thread");
    thread.interrupt();
    //LockSupport.unpark(thread);
}

上面代码中,子线程启动后没有被中断,被阻塞。然后主线程中断该线程,park 方法返回,再次检查中断状态,退出 while 循环。如果中断换成 unpark,while 判断永远为 false,子线程再次被阻塞。

parkNanos & parkUntil

LockSupport.parkNanos(long nanos) 阻塞线程一段时间后返回,参数为纳秒


@Test
public void wakeUpNano() throws Exception {
    System.out.println("before park...");
    LockSupport.parkNanos(3000000000L);
    System.out.println("time is up");
}

LockSupport.parkUntil(long deadline) 阻塞线程到某个时间返回,参数是该时间的毫秒值


@Test
public void wakeUpUntilTime() throws Exception {
    Calendar instance = Calendar.getInstance();
    instance.add(Calendar.SECOND, 3);
    System.out.println("before park...");
    LockSupport.parkUntil(instance.getTimeInMillis());
    System.out.println("time is up");
}

park(Object blocker)

park 系(包括 parkNanos、parkUntil)方法还允许带个 blocker 参数。当线程被阻塞的时候,该参数会被设置到线程内部。

park(Object blocker) 代码


public static void park(Object blocker) {
    Thread t = Thread.currentThread();
    // 设置线程的阻塞对象
    setBlocker(t, blocker);
    // 阻塞线程
    unsafe.park(false, 0L);
    // 被唤醒的时候设置线程阻塞对象为 null
    setBlocker(t, null);
}

其实在 Thread 内部有个 parkBlocker 属性,便是上面的阻塞对象


/**
 * The argument supplied to the current call to
 * java.util.concurrent.locks.LockSupport.park.
 * Set by (private) java.util.concurrent.locks.LockSupport.setBlocker
 * Accessed using java.util.concurrent.locks.LockSupport.getBlocker
 */
volatile Object parkBlocker;

注释中说的很明白,这个参数是提供给 LockSupport.park() 使用的,设置的方法就是 LockSupport.setBlocker(),获取方法是 LockSupport.getBlocker()。


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

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

注意 set 方法和 get 方法的访问权限。set 方法是 private,也就是只能在 LockSupport 内部使用。而 get 方法是 public,可以被任意调用。这也就是这个 blocker 的作用了

线程在阻塞的时候,blocker 会被添加到线程内部(parkBlocker 属性)。当我们使用诊断工具来查看阻塞原因时,诊断工具就可以调用 getBlocker 方法获取 blocker,便可知道当前线程阻塞在哪个对象。官方推荐我们使用 park(Object blocker) 方法来阻塞线程,而且 blocker 设置为 this

看下面例子


@Test
public void parkWithBlocker() throws Exception {
    System.out.println("before park...");
    LockSupport.park(this);
    System.out.println("after park...");
}

代码运行的时候当前线程会阻塞在 LockSupport.park(this) 这里,然后用 jps 查看改程序的 pid

image

再 jstack 1268

image

可以看到主线程正处于 WAITING 状态,是因为 parking 而等待。而且还能知道是在 LockSupportDemo 这个类上等待。

例子

LockSupport 源码中给出了一个例子


public class FIFOMutex {

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

    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());
    }
}

posted @ 2022-06-08 18:32  Tailife  阅读(81)  评论(0编辑  收藏  举报