LockSupport解析与使用

来源:互联网 发布:网络管理规章制度 编辑:程序博客网 时间:2024/06/03 20:00

 赛博朔方  枫叶     

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

源码:
public class LockSupport {    private LockSupport() {} // Cannot be instantiated.    private static void setBlocker(Thread t, Object arg) {        // Even though volatile, hotspot doesn't need a write barrier here.        UNSAFE.putObject(t, parkBlockerOffset, arg);    }    public static void unpark(Thread thread) {        if (thread != null)            UNSAFE.unpark(thread);    }    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);        }    }    public static void parkUntil(Object blocker, long deadline) {        Thread t = Thread.currentThread();        setBlocker(t, blocker);        UNSAFE.park(true, deadline);        setBlocker(t, null);    }    public static Object getBlocker(Thread t) {        if (t == null)            throw new NullPointerException();        return UNSAFE.getObjectVolatile(t, parkBlockerOffset);    }    public static void park() {        UNSAFE.park(false, 0L);    }    public static void parkNanos(long nanos) {        if (nanos > 0)            UNSAFE.park(false, nanos);    }    public static void parkUntil(long deadline) {        UNSAFE.park(true, deadline);    }    static final int nextSecondarySeed() {        int r;        Thread t = Thread.currentThread();        if ((r = UNSAFE.getInt(t, SECONDARY)) != 0) {            r ^= r << 13;   // xorshift            r ^= r >>> 17;            r ^= r << 5;        }        else if ((r = java.util.concurrent.ThreadLocalRandom.current().nextInt()) == 0)            r = 1; // avoid zero        UNSAFE.putInt(t, SECONDARY, r);        return r;    }    // Hotspot implementation via intrinsics API    private static final sun.misc.Unsafe UNSAFE;    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); }    }}


从源码中得到初步信息:
1.不能被实例化(构造函数是私有的)

2.方法都是静态方法


成员变量:
UNSAFEparkBlockerOffsetSEEDPROBESECONDARY

UNSAFE: 

JDK内部用的工具类, 可以直接操控内存,被JDK广泛用于自己的包中.它通过暴露一些Java意义上说“不安全”的功能给Java层代码,来让JDK能够更多的使用Java代码来实现一些原本是平台相关的、需要使用native语言(例如C或C++)才可以实现的功能。该类不应该在JDK核心类库之外使用。


parkBlockerOffset:
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的setBlocker和getBlocker调用
 
这个对象是用来记录线程被阻塞时被谁阻塞的,用于线程监控和分析工具来定位原因的。


偏移量又拿来做什么呢?
parkBlockerOffset = UNSAFE.objectFieldOffset                (tk.getDeclaredField("parkBlocker"));
原来偏移量就算Thread这个类里面变量parkBlocker在内存中的偏移量:

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


 
获取当前线程,通过偏移量的方式设置parkBlocker的值,将调取unsafe.park把线程挂起,线程被恢复后,修改blocker为null


为什么要用偏移量来获取对象?干吗不要直接写个get,set方法?
      parkBlocker就是在线程处于阻塞的情况下才被赋值。线程都已经被阻塞了,如果不通过这种内存的方法,而是直接调用线程内的方法,线程是不会回应调用的。
SEED, PROBE, SECONDARY: 
/** The current seed for a ThreadLocalRandom */    @sun.misc.Contended("tlr")    long threadLocalRandomSeed;
/** Probe hash value; nonzero if threadLocalRandomSeed initialized */    @sun.misc.Contended("tlr")    int threadLocalRandomProbe;

  /** Secondary seed isolated from public ThreadLocalRandom sequence */    @sun.misc.Contended("tlr")    int threadLocalRandomSecondarySeed;
都是Thread类中的内存偏移地址,主要用于ThreadLocalRandom类进行随机数生成,它要比Random性能好很多,可阅读了解详情
 

核心函数

因为LockSupport的核心方法都是基于Unsafe类中的park和unpark方法

         
public native void park(boolean isAbsolute, long time);
public native void unpark(Thread thread);

park: 阻塞线程,线程在一下三种情况下会被堵塞:
    1.调用unpark方法,释放该线程的许可
    2.该线程被中断
    3.到期时间。
isAbsolute为true。
isAbsolute为false。当time为0时,表示无限等待,直到unpark发生。
unpark: 释放线程的堵塞,即修改信号量为1, 同时park返回。


1.调用一次unpark,
如果没有可用线程,则给定许可(permit就变成1(不会累计))
如果有线程被阻塞,接触锁,同时park返回.
如果给定线程没有启动,则该操作不能保证有任何效果.
2.调用park,则会检测permit是否为1;
如果为1则将permit变成0;
如果不为1,则堵塞线程,直到permit变为1.
3.park()和unpark()不会有“Thread.suspend和Thread.resume所可能引发的死锁” 问题,由于许可的存在,调用 park 的线程和另一个试图将其 unpark 的线程之间的竞争将保持活性。
4.如果调用线程被中断,则park方法会返回.
5.park 方法还可以在其他任何时间"毫无理由"地返回,因此通常必须在重新检查返回条件的循环里调用此方法。从这个意义上说,park 是“忙碌等待”的一种优化,它不会浪费这么多的时间进行自旋,但是必须将它与 unpark 配对使用才更高效
6.unpark方法可以先于park调用。比如线程1调用unpark给线程2发了一个“许可“,那么当线程2调用park时,发现已经有“许可”了,那么它会马上再继续运行
public static void park();public static void park() {        UNSAFE.park(false, 0L);// // 获取许可,设置时间为无限长,直到可以获取许可    }


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


public static void park(Object blocker);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方法两次。
private static void setBlocker(Thread t, Object arg) {        UNSAFE.putObject(t, parkBlockerOffset, arg);}public static void parkNanos(Object blocker, long 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(Object blocker, long deadline)public static void parkUntil(Object blocker, long deadline) {        Thread t = Thread.currentThread();        setBlocker(t, blocker);        UNSAFE.park(true, deadline);        setBlocker(t, null);    }

指定的时限前禁用当前线程,除非许可可用
public static void unpark(Thread thread)public static void unpark(Thread thread) {        if (thread != null)            UNSAFE.unpark(thread);    }


线程在 park 上受阻塞,将解除其阻塞状态。否则,预发许可,下一次调用 park 不会受阻塞


LockSupport的park(),unPark()与Thread的wait(),notify()区别
    1.面向的主体不一样。LockSuport主要是针对Thread进进行阻塞处理,可以指定阻塞队列的目标对象,每次可以指定具体的线程唤醒。Object.wait()是以对象为纬度,阻塞当前的线程和唤醒单个(随机)或者所有线程。

    2.实现机制不同。虽然LockSuport可以指定monitor的object对象,但和object.wait(),两者的阻塞队列并不交叉。


代码:
public class LockSupportTest {public static void main(String[] args) throws Exception {TestLockSupport lockSupport = new TestLockSupport();lockSupport.start();//TestThread thread = new TestThread();//thread.start();}}class TestLockSupport extends Thread {public void run() {System.out.println( "TestLockSupport.run()" );LockSupport.park(  );}}class TestThread extends Thread {public void run() {System.out.println( "TestThread.run()" );synchronized (this) {try {wait();} catch (InterruptedException e) {e.printStackTrace();}}}}



使用jstack打印堆栈信息
LockSupport:
"Thread-0" #10 prio=5 os_prio=0 tid=0x000000001d6a5800 nid=0x2ad0 waiting on condition [0x000000001e1cf000]   java.lang.Thread.State: WAITING (parking)        at sun.misc.Unsafe.park(Native Method)        at java.util.concurrent.locks.LockSupport.park(LockSupport.java:304)        at TestLockSupport.run(LockSupportTest.java:16)


可以看出当前线程状态时等待的,用的是Unsafe.park挂起的


Thread:
"Thread-0" #10 prio=5 os_prio=0 tid=0x000000001d321000 nid=0x8a8 in Object.wait() [0x000000001e2af000]   java.lang.Thread.State: WAITING (on object monitor)        at java.lang.Object.wait(Native Method)        - waiting on <0x000000076b257698> (a TestThread)        at java.lang.Object.wait(Object.java:502)        at TestThread.run(LockSupportTest.java:23)        - locked <0x000000076b257698> (a TestThread)


可以看出是用Object.wait实现的


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

结论:

     Thread的notify也是在实际使用过程只需要斟酌的事情,notifyAll唤醒所有线程;notify唤醒单个线程,如果有两个被阻塞线程….

          LockSupport的park也可以响应Thread的interrupt中断,后面会有例子详讲.




案例
先park,后unpark
public class LockSupportTest {public static void main(String[] args) throws Exception {TestThread thread = new TestThread( Thread.currentThread() );thread.start();System.out.println( "before park" );// 等待获取许可LockSupport.park( "Park" );System.out.println( "after park" );}}

class TestThread extends Thread {private Object object;public TestThread(Object object) {this.object = object;}public void run() {System.out.println( "before unpark" );// 休眠,保证setBlocker(t, blocker)try {Thread.sleep( 500 );} catch (InterruptedException e) {e.printStackTrace();}// 获取blockerSystem.out.println( "Blocker info: " + LockSupport.getBlocker( (Thread) object ) );// 释放许可LockSupport.unpark( (Thread) object );// 休眠500ms,保证先执行park中的setBlocker(t, null);try {Thread.sleep( 500 );} catch (InterruptedException e) {e.printStackTrace();}System.out.println( "Blocker info: " + LockSupport.getBlocker( (Thread) object ) );System.out.println( "after unpark" );}}


结果:
before parkbefore unparkBlocker info Parkafter parkBlocker info nullafter unpark


先执行park,然后在执行unpark,进行同步,并且在unpark的前后都调用了getBlocker,可以看到两次的结果不一样,第二次调用的结果为null,这是因为在调用unpark之后,执行了Lock.park(Object blocker) 方法中的setBlocker(t, null) 方法,所以第二次调用getBlocker时为null


先unpark,后park
public class LockSupportTest {public static void main(String[] args) throws Exception {TestThread thread = new TestThread( Thread.currentThread() );long start = System.nanoTime();thread.start();try {Thread.sleep( 1000 );} catch (InterruptedException e) {e.printStackTrace();}System.out.println( "before park" );// 等待获取许可LockSupport.park( "Park" );System.out.println( "after park:" + (System.nanoTime() - start) );}}
class TestThread extends Thread {private Object object;public TestThread(Object object) {this.object = object;}public void run() {System.out.println( "before unpark" );LockSupport.unpark( (Thread) object );System.out.println( "after unpark" );}}

before unparkafter unparkbefore parkafter park:3000046392



先执行unpark,在调用park,直接就没被阻塞, 因此park/unpark相比wait/notify更加的灵活


interrupt与unpark
public class LockSupportTest {public static void main(String[] args) throws Exception {TestThread thread = new TestThread();thread.start();try {Thread.sleep( 1000 );} catch (InterruptedException e) {e.printStackTrace();}thread.interrupt();}}
class TestThread extends Thread {public void run() {System.out.println( Thread.currentThread().isInterrupted() );LockSupport.park();System.out.println( Thread.currentThread().isInterrupted() );}}

falsetrue


可以看到子线程被阻塞后,主线程调用子线程中断,子线程则继续执行,并没有抛出InterruptedException, 这里依赖的是Interrupted status, 如果线程被中断而退出阻塞该状态会被修改为true, 可见interrupt起到的作用与unpark一样.


总结
简而言之:
1.实现机制和wait/notify有所不同,面向的是线程
2.不需要依赖监视器
3.与wait/notify没有交集
4.使用起来方便灵活



原创粉丝点击