LockSupport-线程阻塞原语

基本介绍

基本的线程阻塞原语,被用于创建锁和其他同步类上。长久以来对线程阻塞与唤醒经常我们会使用object的wait和notify,除了这种方式,JDK1.6之后并发包还提供了另外一种方式对线程进行挂起和恢复,它就是并发包子包locks提供的LockSupport。

使用实例与简介

public static void main(String[] args) throws InterruptedException {
    Thread t = new Thread(
        ()->{
            System.out.println("周末要打游戏!");
            LockSupport.park();
            System.out.println("陪女朋友逛街逛街!");
        }
    );
    t.start();
    Thread.sleep(1000);
    LockSupport.unpark(t);
    System.out.println("女朋友叫着男票逛街逛街逛街!");

}

上面的输出是:

周末要打游戏!
女朋友叫着男票逛街逛街逛街!
陪女朋友逛街逛街!

上面的流程十分简单:
1)先启动一个线程t,并直接调用start()方法,之后main线程sleep;
2)在线程t里面输出 “周末要打游戏!” 之后调用 LockSupport.park(); 阻塞当前线程,这时候线程A就会阻塞住。
3)主线程sleep一定时间之后会苏醒,然后调用 LockSupport.unpark(thread); 唤醒线程t;
4)之后就是正常的流程。

从上面我们可以看出:

  • 实现机制和wait/notify有所不同,面向的是线程。
  • 不需要依赖监视器
  • 使用起来十分灵活简便

LockSupport 和 CAS 是Java并发包中很多并发工具控制机制的基础,它们底层其实都是依赖Unsafe实现。

LockSupport是用来创建锁和其他同步类的基本线程阻塞原语。LockSupport 提供park()和unpark()方法实现阻塞线程和解除线程阻塞,LockSupport和每个使用它的线程都与一个许可(permit)关联。permit相当于1,0的开关,默认是0,调用一次unpark就加1变成1,调用一次park会消费permit, 也就是将1变成0,同时park立即返回。再次调用park会变成block(因为permit为0了,会阻塞在这里,直到permit变为1), 这时调用unpark会把permit置为1。每个线程都有一个相关的permit, permit最多只有一个,重复调用unpark也不会积累。此外可以允许先调用unpark再调用park,这时调用park不会阻塞。

park()和unpark()不会有 “Thread.suspend和Thread.resume所可能引发的死锁” 问题,由于许可的存在,调用park的线程和另一个试图将其unpark的线程之间的竞争将保持活性。

如果调用线程被中断,则park方法会返回。同时park也拥有可以设置超时时间的版本。

需要特别注意的一点:park 方法还可以在其他任何时间“毫无理由”地返回,因此通常必须在重新检查返回条件的循环里调用此方法。从这个意义上说,park 是“忙碌等待”的一种优化,它不会浪费这么多的时间进行自旋,但是必须将它与 unpark 配对使用才更高效。

第二个例子:unpark()先调用

public static void main(String[] args) throws InterruptedException {
    Thread thread = new Thread(
        ()->{
            System.out.println("周末要打游戏!");
            LockSupport.park();
            System.out.println("陪女朋友逛街逛街!");
        }
    );
    thread.start();
    LockSupport.unpark(thread);
    Thread.sleep(5000);
    System.out.println("女朋友叫着男票逛街逛街逛街!");
}

该方法的返回值是:

周末要打游戏!
陪女朋友逛街逛街!
// 此处要等待五秒
女朋友叫着男票逛街逛街逛街!

可以调用LockSupport.park();并不会阻塞。

LockSupport的源码分析

LockSupport中主要的两个成员变量:

private static final sun.misc.Unsafe UNSAFE;
private static final long parkBlockerOffset;

unsafe:全名sun.misc.Unsafe可以直接操控内存,被JDK广泛用于自己的包中,如java.nio和java.util.concurrent。但是不建议在生产环境中使用这个类。因为这个API十分不安全、不轻便、而且不稳定。
LockSupport的方法底层都是调用Unsafe的方法实现。

再来看parkBlockerOffset:
parkBlocker就是第一部分说到的用于记录线程被谁阻塞的,用于线程监控和分析工具来定位原因的,可以通过LockSupport的getBlocker获取到阻塞的对象。

static {
    try {
        UNSAFE = sun.misc.Unsafe.getUnsafe();
        Class<?> tk = Thread.class;
        parkBlockerOffset = UNSAFE.objectFieldOffset
            (tk.getDeclaredField("parkBlocker"));
    } catch (Exception ex) { throw new Error(ex); }
 }

从这个静态语句块可以看的出来,先是通过反射机制获取Thread类的parkBlocker字段对象。然后通过sun.misc.Unsafe对象的objectFieldOffset方法获取到parkBlocker在内存里的偏移量,parkBlockerOffset的值就是这么来的.

JVM的实现可以自由选择如何实现Java对象的“布局”,也就是在内存里Java对象的各个部分放在哪里,包括对象的实例字段和一些元数据之类。 sun.misc.Unsafe里关于对象字段访问的方法把对象布局抽象出来,它提供了objectFieldOffset()方法用于获取某个字段相对 Java对象的“起始地址”的偏移量,也提供了getInt、getLong、getObject之类的方法可以使用前面获取的偏移量来访问某个Java 对象的某个字段。

为什么要用偏移量来获取对象?干吗不要直接写个get,set方法。多简单?
仔细想想就能明白,这个parkBlocker就是在线程处于阻塞的情况下才会被赋值。线程都已经阻塞了,如果不通过这种内存的方法,而是直接调用线程内的方法,线程是不会回应调用的。

猜你喜欢

转载自blog.csdn.net/u010853261/article/details/81483095
今日推荐