java 并发之LockSupport

LockSupport

LockSupport提供park()和unpark()方法实现阻塞线程和解除线程阻塞,实现的阻塞和解除阻塞是基于”许可(permit)”作为关联,permit相当于一个信号量(0,1),默认是0. 线程之间不再需要一个Object或者其它变量来存储状态,不再需要关心对方的状态.

wait()和notify()、notifyAll()的比较

public static void main(String[] args) {
       final Object lock = new Object();
       ExecutorService service = Executors.newCachedThreadPool();
       service.submit(new Runnable() {
           @Override
           public void run() {
               try {
                   TimeUnit.SECONDS.sleep(1);
                   System.out.println("进入等待");
                   synchronized (lock) {
                       lock.wait();
                   }
                   System.out.println("结束等待");
               } catch (InterruptedException e) {
                   e.printStackTrace();
               }
           }
       });
       service.submit(new Runnable() {
           @Override
           public void run() {
               synchronized (lock){
                   lock.notifyAll();
                   System.out.println("通知所有线程结束等待");
               }
           }
       });

       while(!service.isShutdown());
   }

测试结果:

通知所有线程结束等待
进入等待

测试完,会发现线程并没有结束等待。如果我们使用LockSupport,则不会出现这种情况。

final Thread t1 = new Thread() {
            @Override
            public void run() {
                try {
                    TimeUnit.SECONDS.sleep(1);
                    System.out.println("进入等待");
                    LockSupport.park(this);
                    System.out.println("结束等待");
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        };
        t1.start();
        final Thread t2 = new Thread() {
            @Override
            public void run() {
                LockSupport.unpark(t1);
                System.out.println("通知结束等待");
            }
        };
        t2.start();

        while(t1.isAlive() || t2.isAlive());
        System.out.println("代码结束");

测试结果:

通知结束等待
进入等待
结束等待
代码结束

LockSupport阻塞和解除阻塞线程直接操作的是Thread,而Object的wait/notify它并不是直接对线程操作,它是被动的方法,它需要一个object来进行线程的挂起或唤醒.
Thead在调用wait之前, 当前线程必须先获得该对象的监视器(synchronized),被唤醒之后需要重新获取到监视器才能继续执行.而LockSupport可以随意进行park或者unpark.

原理解释

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也不会积累。

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

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

三种形式的 park 还各自支持一个 blocker 对象参数。此对象在线程受阻塞时被记录,以允许监视工具和诊断工具确定线程受阻塞的原因。(这样的工具可以使用方法 getBlocker(java.lang.Thread) 访问 blocker。)建议最好使用这些形式,而不是不带此参数的原始形式。在锁实现中提供的作为 blocker 的普通参数是 this。

源码解读

LockSupport中主要的两个成员变量

// Hotspot implementation via intrinsics API
    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就是在线程处于阻塞的情况下才会被赋值。线程都已经阻塞了,如果不通过这种内存的方法,而是直接调用线程内的方法,线程是不会回应调用的。

LockSupport的其他变量

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

都是Thread类中的内存偏移地址,主要用于ThreadLocalRandom类进行随机数生成,它要比Random性能好很多,可阅读了解详情。

LockSupport中的方法

park方法

public static void park(Object blocker) {
    Thread t = Thread.currentThread();
    setBlocker(t, blocker);
    UNSAFE.park(false, 0L);
    setBlocker(t, null);
}

先获取当前线程,设置当前线程的parkBlocker字段(parkBlocker), 调用Unsafe类的park方法,最后再次调用setBlocker,为什么呢?

因为当前线程首先设置好parkBlocker字段后再调用Unsafe的park方法,之后,当前线程已经被阻塞,等待unpark方法被调用, unpark方法被调用,该线程获得许可后,可以继续进行下面的代码,第二个setBlocker参数parkBlocker字段设置为null,这样就完成了整个park方法的逻辑. 如果没有第二个setBlocker,那么之后没有调用park(Object blocker),而直接调用getBlocker方法,得到的还是前一个park(Object blocker)设置的blocker,显然是不符合逻辑的。 所以,park(Object) 方法里必须要调用setBlocker方法两次。

/** 禁用当前线程并等待多长时间. */
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(Object blocker, long deadline) {
    Thread t = Thread.currentThread();
    setBlocker(t, blocker);
    UNSAFE.park(true, deadline);
    setBlocker(t, null);
}

调用了park方法后,会禁用当前线程,除非许可可用,在以下三种情况发生之前,线程处于阻塞状态:
1.其他某个线程将当前线程作为目标调用 unpark
2.其他某个线程中断当前线程
3.该调用不合逻辑地(即毫无理由地)返回

unpark方法

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

猜你喜欢

转载自blog.csdn.net/liu20111590/article/details/82191971