LockSupport--Java中锁机制的基础工具

/ 默认分类 / 0 条评论 / 1220浏览

LockSupport-java中锁机制的基础工具

前言

LockSupport类时jdk中的一个工具类,并且这是java中的锁实现的基础工具,LockSupport其实也是对Unsafe的封装,可以直接操作线程;
所以想要了解LockSupport就需要理解Unsafe,Unsafe的基础功能和原理在我之前的文章中有介绍过,下面通过LockSupport对Unsafe中的几个重要的操作 线程的方法进行详细说明下.

1. park()

LockSupport中对park方法进行的封装如下,调用park的线程可能会发生阻塞挂起,具体在下面详细介绍

    public static void park() {
        UNSAFE.park(false, 0L);
    }

2. unpark()

unpark方法会向参数中的thread发放许可证,可以让之前因为调用park而阻塞的线程返回

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

下面我用一个生活中的例子来理解下park和unpark,并解释为什么调用park的线程也不一定会发生阻塞

park顾名思义表示停车的意思,可以理解为上高速的收费站,将线程理解为车辆,刚开始上高速之前所有的车都没有高速路许可证,调用park方法表示 车辆到达收费站停下,收费员检查车辆发现没有许可证,所以就不放行,等于线程阻塞了,之后如果调用unpark,表示给该车(该线程)开了通行许可证 这个时候车辆(线程)就持有许可证了,等于线程阻塞返回了.

同理,如果线程刚开始没有调用park,而是直接调用unpark,那么等于刚开始车辆还没有驶入收费站就已经拿到了通行许可证,此时调用park方法(同样的表示 车辆到达收费站,收费员要检查他的许可证),收费员发现他有许可证了,直接通行,不需要阻塞等待.

另外需要注意一点,调用park方法阻塞的线程可以被其他线程调用该阻塞线程的interrupt方法被中断返回,但是被中断返回后不会抛出InterruptException
这一点和Object中的wait(),Thread中的sleep()不同,他们会抛出InterruptException异常.

按照上面的介绍可以发现,park阻塞后的线程如果返回了,可能有两种原因,一个是调用了unpark,另一个可能是调用了interrupt中断,所以如果有需要,比如只允许 unpark来唤醒阻塞的线程,那么最好在while循环中调用park方法,判断是中断导致的park方法返回还是unpark导致的park方法返回

    public static void main(String[] args) throws InterruptedException {
        Thread thread = new Thread(() -> {
            //不是中断就一直park,不返回(等于park阻塞了),只有发生中断,while才会退出,也就表示线程被唤醒了,不阻塞了
            while (!Thread.currentThread().isInterrupted()){
                LockSupport.park();
            }
            System.out.println("中断导致park返回");
        });

        thread.start();
        System.out.println("尝试unpark唤醒park的线程");
        LockSupport.unpark(thread);
        TimeUnit.MILLISECONDS.sleep(100);
        System.out.println("尝试中断唤醒park的线程");
        thread.interrupt();
    }

console:

尝试unpark唤醒park的线程
尝试中断唤醒park的线程
中断导致park返回
public static void main(String[] args) throws InterruptedException {
        Thread thread = new Thread(() -> {
            LockSupport.park();
            while (true) {
                if (Thread.interrupted()) {
                    System.out.println("本次为调用中断,不返回,继续park,等于线程继续阻塞");
                    LockSupport.park();
                } else {
                    System.out.println("本次为调用unpark,可以,park返回,等于线程被唤醒");
                    break;
                }
            }
            System.out.println("我被unpark唤醒了,开始继续执行,balabala......");
        });
        thread.start();
        //保证thread线程开始执行了park
        TimeUnit.MILLISECONDS.sleep(1000);
        System.out.println("尝试中断唤醒park的线程");
        thread.interrupt();
        TimeUnit.MILLISECONDS.sleep(1000);
        System.out.println("尝试unpark唤醒park的线程");
        LockSupport.unpark(thread);
    }

console:

尝试中断唤醒park的线程
本次为调用中断,不返回,继续park,等于线程继续阻塞
尝试unpark唤醒park的线程
本次为调用unpark,可以,park返回,等于线程被唤醒
我被unpark唤醒了,开始继续执行,balabala......
  1. parkNanos(long nanos)
    public static void parkNanos(long nanos) {
        if (nanos > 0)
            UNSAFE.park(false, nanos);
    }

按经验猜测下应该也知道,nanos是阻塞的超时时长.所以parkNanos调用如果阻塞后,唤醒可能原因又增加了一个,可能是因为超时导致返回, 所以如果需要判断是哪一种方式导致的返回,需要加一个计时操作,关于这一点在jdk的注释中也有提到:

     * <p>This method does <em>not</em> report which of these caused the
     * method to return. Callers should re-check the conditions which caused
     * the thread to park in the first place. Callers may also determine,
     * for example, the interrupt status of the thread, or the elapsed time
     * upon return.
  1. 可以设置线程blocker变量的api

park(Object blocker) void parkNanos(Object blocker, long nanos)

    public static void park(Object blocker) {
        Thread t = Thread.currentThread();
        setBlocker(t, blocker);
        UNSAFE.park(false, 0L);
        setBlocker(t, null);
    }
    
    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);
        }
    }

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

之间介绍了Unsafe,这里不赘述,按照源码我们可以在LockSupport中找到parkBlockerOffset

UNSAFE = sun.misc.Unsafe.getUnsafe();
            Class<?> tk = Thread.class;
parkBlockerOffset = UNSAFE.objectFieldOffset
                (tk.getDeclaredField("parkBlocker"));

所以很明白了,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中的Object参数就是对当前线程的变量parkBlocker进行操作
park之前为该标志设置,park返回后清除该标志