JUC学习记录(三)- LockSupport

496 阅读3分钟

LockSupport

定义:线程阻塞的工具类,用于线程阻塞与释放;通常作为锁实现的工具类;其中主要方法为park()、unpark(),实现借助Unsafe;

park-意为等待许可

unpark-意为许可

数据结构

  1. 私有构造

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

  2. 属性

    	// Hotspot implementation via intrinsics API
    	// UNSAFE实例
        private static final sun.misc.Unsafe UNSAFE;
    	// 用于记录线程被谁阻塞的
    	// 获取Thread类中属性的偏移量
        private static final long parkBlockerOffset;
        private static final long SEED;
        private static final long PROBE;
        private static final long SECONDARY;
        static {
            try {
                UNSAFE = sun.misc.Unsafe.getUnsafe();
                Class<?> tk = Thread.class;
                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); }
        }
    

上文提到了许可"permit",现在对其稍作解释:

LockSupport与每一个使用他的线程都与一个permit相关联,其最多有一个。

permit相当于线程是否阻塞、释放的开关,默认值为0(1,0):

  • LockSupport.unpark() - permit+=1, 即permit = 1
  • LockSupport.park() - permit -= 1,即permit = 0
  • 再次调用LockSupport.park() , 则线程阻塞: 因为permit的值为0,它需要等待LockSupport.unpark() 调用,否则就一直被阻塞直到permit值为1

LockSupport的常用方法

  1. set/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);
        }
    
     /**
         * Returns the blocker object supplied to the most recent
         * invocation of a park method that has not yet unblocked, or null
         * if not blocked.  The value returned is just a momentary
         * snapshot -- the thread may have since unblocked or blocked on a
         * different blocker object.
         *
         * @param t the thread
         * @return the blocker
         * @throws NullPointerException if argument is null
         * @since 1.6
         * 获取最近一次尚未解除阻塞的被park方法调用的blocker对象,如果该调用未阻塞,则返回null
         */
        public static Object getBlocker(Thread t) {
            if (t == null)
                throw new NullPointerException();
            return UNSAFE.getObjectVolatile(t, parkBlockerOffset);
        }
    
  2. park

    作用:

    等待许可(即阻塞),调用时会发生两种情况:

    • 当许可可用时,消耗许可并返回
    • 当许可不可用时,线程阻塞等待许可
        // 
    	public static void park() {
            UNSAFE.park(false, 0L);
        }
        
        public static void park(Object blocker) {
            Thread t = Thread.currentThread();
            setBlocker(t, blocker);
            UNSAFE.park(false, 0L);
            setBlocker(t, null);
        }
        
    	// 纳秒-超时返回
        public static void parkNanos(long nanos) {
            if (nanos > 0)
                UNSAFE.park(false, nanos);
        }
        
        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);
            }
        }
        
    	// 毫秒级-直到这个时间返回
        public static void parkUntil(long deadline) {
            UNSAFE.park(true, deadline);
        }
        
        public static void parkUntil(Object blocker, long deadline) {
            Thread t = Thread.currentThread();
            setBlocker(t, blocker);
            UNSAFE.park(true, deadline);
            setBlocker(t, null);
        }
    
  3. unpark

    作用:

    获取许可-释放锁;

    • 如果许可不可用,则变为可用,释放锁
    • 如果许可可用,调用LockSupport.park()时则不会阻塞
        // 释放(线程)其许可以及锁
    	public static void unpark(Thread thread) {
            if (thread != null)
                UNSAFE.unpark(thread);
        }
    

更多

LockSupport不可重入

        LockSupport.unpark(Thread.currentThread());
        System.out.println("执行unpark");
        LockSupport.park();
        System.out.println("执行第一次park");
        LockSupport.park();
        System.out.println("执行第二次park");

        // sout = 执行unpark \n 执行第一次park

LockSupport可中断

中断响应: 线程在park阻塞状态下仍然能够被中断且不抛出异常

        Thread thread = new Thread(() -> {
            long start = System.currentTimeMillis();
            while ((System.currentTimeMillis() - start) <= 1000);//空转1s
            System.out.println("空转1S结束");
            // 阻塞
            LockSupport.park();
            System.out.println(Thread.currentThread().getName() + " 是否被中断:" + Thread.currentThread().isInterrupted());
        },"TH");

        thread.start();
        thread.interrupt();
// sout 空转1S结束
// TH 是否被中断:true

LockSupport.park/unpark VS Object.wait\notify

  1. park 不需要获取对象的锁,wait必须先获取对象的锁
  2. unpark 可以先于 park执行
  3. unpark 可以指定唤醒的线程,而notify不可控
  4. LockSupport 可指定Thread
  5. Object.wait\notify 只能在获取锁的前提下执行-即synchronized修饰的代码块

使用例子-源码中提供

先进先出非重入锁的实现

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