我学JUC之LockSupport

LockSupport是基本的线程阻塞原语, 用于构建lock和其他同步类.

这个类将每个线程与一个permit进行关联(类似于java.util.concurrent.Semaphore一样的感觉). 如果permit可用的话, 调用park方法会消费掉permit, 然后直接返回. 否则, 会阻塞线程. 调用unpark会使得不可用的permit可用.(和Semaphore不同的是, permit不会累加, 每个线程最多只有一个permit).

因为使用了permit, park和unpark方法提供了有效的block和unblock线程的方法. 与已经废弃的Thread.suspend和Thread.resume方法不同. 线程A调用park, 另一个线程B调用unpark来unblock A, 这两者发生竞争, 仍然会使得线程A保持活性.

另外, 如果调用者线程被interrupted, park会返回, 同时park也有timeout支持. park方法可能会随时无原因的返回, 所以一般调用需要在一个检测condition是否可用的循环中使用. 在这个意义上, park可以当作是一个busy wait, 而不需要太多时间自旋.

三种形式的park都支持传入blocker参数. 这个对象在线程被阻塞等待permit时被记录下来, 可用于监控和诊断工具来分析当前线程因为何种原因而阻塞(这些工具通过使用getBlocker(Thread)方法来获取). 推荐使用这种能够传入bolcker对象参数的方法类型. 在lock实现中, 通常传入的blocker参数是this

这些方法同样也被设计用于创建更高层次的同步工具.
park方法被设计只用于以下的形式:

while (!canProceed()) { ... LockSupport.park(this); }}

在上面的代码中, park方法之前的, 无论是canProceed或其他方法都不应该涉及locking或block. 因为每个线程只有一个permit, 任何中间形式的使用park都会干扰想要的效果.
下面是一个简单的FIFO非重入锁的实现.

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

LockSupport的方法:

//使用一个线程thread的permit可用. 如果这个thread之前阻塞在park方法上, 那么会unblock. 或者thread没有阻塞在park方法上, 下一次park方法时, thread会直接返回, 而不会阻塞. 不能保证线程没有启动时有效.
public static void unpark(Thread thread)

/**禁止程被调度, 除非permit可用
如果permit可用, 那么就会被直接消费, 然后park调用返回. 否则当前线程会被停止调度, 保持休眠状态直到以下三个条件之一发生:
1. 另外一个线程对其调用了unpark
2. 另外一个线程调用了Thread.interrupt(thread)方法, interrupt当前线程
3. 毫无理由的返回
这个方法并不会报告什么原因造成了返回, 调用者应该立即检查造成thread调用park的Condition, 同时, 调用者也可能需要检查thread的interrupt状态.
**/
public static void park(Object blocker)

//说明与上相同, nanos是线程阻塞的最长的纳秒数
public static void parkNanos(Object blocker, long nanos)
//说明同上. deadline是线程要阻塞到的绝对时间, Epoch时间戳, 单位为毫秒
public static void parkUntil(Object blocker, long deadline)

public static void park()
public static void parkNanos(long nanos)
public static void parkUntil(long deadline)

//获取某个Thread最近调用park时传入的blocker. 每次可能返回不同的值
public static Object getBlocker(Thread t)
//设置一个线程的blocker对象
private static void setBlocker(Thread t, Object arg)

blocker的原理:

设置blocker时设置的是Thread的一个字段parkBlocker, 通过UNSAFE类来获取其在Thread类中的偏移, 然后通过UNSAFE类在这个偏移上放置blocker的指针就可以了.

    static {
        try {
            UNSAFE = sun.misc.Unsafe.getUnsafe();
            Class<?> tk = Thread.class;
            //通过UNSAFE类来获取Thread类中的parkBlocker在内存中在对象中的偏移
            parkBlockerOffset = UNSAFE.objectFieldOffset
                (tk.getDeclaredField("parkBlocker"));
            .....
        } catch (Exception ex) { throw new Error(ex); }
    }

    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) {
        //使用UNSAFE的方法在对象的相应偏移位置放置对象指针
        UNSAFE.putObject(t, parkBlockerOffset, arg);
    }
    原文作者:JUC
    原文地址: https://blog.csdn.net/haoyifen/article/details/61926619
    本文转自网络文章,转载此文章仅为分享知识,如有侵权,请联系博主进行删除。
点赞