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核心类库之外使用。
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 不会受阻塞
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
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更加的灵活
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.使用起来方便灵活
- LockSupport解析与使用
- LockSupport使用
- java线程阻塞中断与LockSupport使用介绍
- Thread.interrupt与LockSupport
- LockSupport的使用
- JDK7中LockSupport源码解析
- LockSupport
- LockSupport
- LockSupport
- LockSupport
- LockSupport
- Java 线程同步基础类 LockSupport 解析
- 【技能库】--LockSupport的使用(243)
- 解析Concurrent包下的ReentrantLock、LockSupport、AQS
- java并发库中的LockSupport介绍及使用
- Volley使用与解析
- EventBus使用与解析
- RESideMenu使用与解析
- 玩转 WebView ,突破系统限制,让缓存更简单,更灵活
- 【Machine Learning】OpenCV中的K-means聚类
- 助你在精进技术的道路上一臂之力
- switch default 小计
- 使用kotlin
- LockSupport解析与使用
- React学习笔记_realword
- LINTCODE——计算最大值
- HBase各版本对Hadoop版本的支持情况
- HDU2819 Swap
- java递归算法中的排列组合问题及排列组合去重
- 删除了Android Private Libraries之后可重新添加
- MySql数据库中建立索引
- HDU4764 Stone (巴什博奕)