这是java高并发系列第14篇文章。java
本文主要内容:微信
LockSupport位于java.util.concurrent(简称juc)包中,算是juc中一个基础类,juc中不少地方都会使用LockSupport,很是重要,但愿你们必定要掌握。并发
关于线程等待/唤醒的方法,前面的文章中咱们已经讲过2种了:ide
这2种方式,咱们先来看一下示例。高并发
package com.itsoku.chat10; import java.util.concurrent.TimeUnit; /** * 微信公众号:javacode2018,获取年薪50万课程 */ public class Demo1 { static Object lock = new Object(); public static void main(String[] args) throws InterruptedException { Thread t1 = new Thread(() -> { synchronized (lock) { System.out.println(System.currentTimeMillis() + "," + Thread.currentThread().getName() + " start!"); try { lock.wait(); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println(System.currentTimeMillis() + "," + Thread.currentThread().getName() + " 被唤醒!"); } }); t1.setName("t1"); t1.start(); //休眠5秒 TimeUnit.SECONDS.sleep(5); synchronized (lock) { lock.notify(); } } }
输出:工具
1563592938744,t1 start! 1563592943745,t1 被唤醒!
t1线程中调用lock.wait()
方法让t1线程等待,主线程中休眠5秒以后,调用lock.notify()
方法唤醒了t1线程,输出的结果中,两行结果相差5秒左右,程序正常退出。线程
咱们把上面代码中main方法内部改一下,删除了synchronized
关键字,看看有什么效果:code
package com.itsoku.chat10; import java.util.concurrent.TimeUnit; /** * 微信公众号:javacode2018,获取年薪50万课程 */ public class Demo2 { static Object lock = new Object(); public static void main(String[] args) throws InterruptedException { Thread t1 = new Thread(() -> { System.out.println(System.currentTimeMillis() + "," + Thread.currentThread().getName() + " start!"); try { lock.wait(); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println(System.currentTimeMillis() + "," + Thread.currentThread().getName() + " 被唤醒!"); }); t1.setName("t1"); t1.start(); //休眠5秒 TimeUnit.SECONDS.sleep(5); lock.notify(); } }
运行结果:对象
Exception in thread "t1" java.lang.IllegalMonitorStateException 1563593178811,t1 start! at java.lang.Object.wait(Native Method) at java.lang.Object.wait(Object.java:502) at com.itsoku.chat10.Demo2.lambda$main$0(Demo2.java:16) at java.lang.Thread.run(Thread.java:745) Exception in thread "main" java.lang.IllegalMonitorStateException at java.lang.Object.notify(Native Method) at com.itsoku.chat10.Demo2.main(Demo2.java:26)
上面代码中将synchronized去掉了,发现调用wait()方法和调用notify()方法都抛出了IllegalMonitorStateException
异常,缘由:Object类中的wait、notify、notifyAll用于线程等待和唤醒的方法,都必须在同步代码中运行(必须用到关键字synchronized)。token
唤醒方法在等待方法以前执行,线程可以被唤醒么?代码以下:
package com.itsoku.chat10; import java.util.concurrent.TimeUnit; /** * 微信公众号:javacode2018,获取年薪50万课程 */ public class Demo3 { static Object lock = new Object(); public static void main(String[] args) throws InterruptedException { Thread t1 = new Thread(() -> { try { TimeUnit.SECONDS.sleep(5); } catch (InterruptedException e) { e.printStackTrace(); } synchronized (lock) { System.out.println(System.currentTimeMillis() + "," + Thread.currentThread().getName() + " start!"); try { //休眠3秒 lock.wait(); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println(System.currentTimeMillis() + "," + Thread.currentThread().getName() + " 被唤醒!"); } }); t1.setName("t1"); t1.start(); //休眠1秒以后唤醒lock对象上等待的线程 TimeUnit.SECONDS.sleep(1); synchronized (lock) { lock.notify(); } System.out.println("lock.notify()执行完毕"); } }
运行代码,输出结果:
lock.notify()执行完毕 1563593869797,t1 start!
输出了上面2行以后,程序一直没法结束,t1线程调用wait()方法以后没法被唤醒了,从输出中可见,notify()
方法在wait()
方法以前执行了,等待的线程没法被唤醒了。说明:唤醒方法在等待方法以前执行,线程没法被唤醒。
关于Object类中的用户线程等待和唤醒的方法,总结一下:
Condition的使用,前面的文章讲过,对这块不熟悉的能够移步JUC中Condition的使用,关于Condition咱们准备了3个示例。
package com.itsoku.chat10; import java.util.concurrent.TimeUnit; import java.util.concurrent.locks.Condition; import java.util.concurrent.locks.ReentrantLock; /** * 微信公众号:javacode2018,获取年薪50万课程 */ public class Demo4 { static ReentrantLock lock = new ReentrantLock(); static Condition condition = lock.newCondition(); public static void main(String[] args) throws InterruptedException { Thread t1 = new Thread(() -> { lock.lock(); try { System.out.println(System.currentTimeMillis() + "," + Thread.currentThread().getName() + " start!"); try { condition.await(); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println(System.currentTimeMillis() + "," + Thread.currentThread().getName() + " 被唤醒!"); } finally { lock.unlock(); } }); t1.setName("t1"); t1.start(); //休眠5秒 TimeUnit.SECONDS.sleep(5); lock.lock(); try { condition.signal(); } finally { lock.unlock(); } } }
输出:
1563594349632,t1 start! 1563594354634,t1 被唤醒!
t1线程启动以后调用condition.await()
方法将线程处于等待中,主线程休眠5秒以后调用condition.signal()
方法将t1线程唤醒成功,输出结果中2个时间戳相差5秒。
咱们将上面代码中的lock.lock()、lock.unlock()去掉,看看会发生什么。代码:
package com.itsoku.chat10; import java.util.concurrent.TimeUnit; import java.util.concurrent.locks.Condition; import java.util.concurrent.locks.ReentrantLock; /** * 微信公众号:javacode2018,获取年薪50万课程 */ public class Demo5 { static ReentrantLock lock = new ReentrantLock(); static Condition condition = lock.newCondition(); public static void main(String[] args) throws InterruptedException { Thread t1 = new Thread(() -> { System.out.println(System.currentTimeMillis() + "," + Thread.currentThread().getName() + " start!"); try { condition.await(); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println(System.currentTimeMillis() + "," + Thread.currentThread().getName() + " 被唤醒!"); }); t1.setName("t1"); t1.start(); //休眠5秒 TimeUnit.SECONDS.sleep(5); condition.signal(); } }
输出:
Exception in thread "t1" java.lang.IllegalMonitorStateException 1563594654865,t1 start! at java.util.concurrent.locks.ReentrantLock$Sync.tryRelease(ReentrantLock.java:151) at java.util.concurrent.locks.AbstractQueuedSynchronizer.release(AbstractQueuedSynchronizer.java:1261) at java.util.concurrent.locks.AbstractQueuedSynchronizer.fullyRelease(AbstractQueuedSynchronizer.java:1723) at java.util.concurrent.locks.AbstractQueuedSynchronizer$ConditionObject.await(AbstractQueuedSynchronizer.java:2036) at com.itsoku.chat10.Demo5.lambda$main$0(Demo5.java:19) at java.lang.Thread.run(Thread.java:745) Exception in thread "main" java.lang.IllegalMonitorStateException at java.util.concurrent.locks.AbstractQueuedSynchronizer$ConditionObject.signal(AbstractQueuedSynchronizer.java:1939) at com.itsoku.chat10.Demo5.main(Demo5.java:29)
有异常发生,condition.await();
和condition.signal();
都触发了IllegalMonitorStateException
异常。缘由:调用condition中线程等待和唤醒的方法的前提是必需要先获取lock的锁。
唤醒代码在等待以前执行,线程可以被唤醒么?代码以下:
package com.itsoku.chat10; import java.util.concurrent.TimeUnit; import java.util.concurrent.locks.Condition; import java.util.concurrent.locks.ReentrantLock; /** * 微信公众号:javacode2018,获取年薪50万课程 */ public class Demo6 { static ReentrantLock lock = new ReentrantLock(); static Condition condition = lock.newCondition(); public static void main(String[] args) throws InterruptedException { Thread t1 = new Thread(() -> { try { TimeUnit.SECONDS.sleep(5); } catch (InterruptedException e) { e.printStackTrace(); } lock.lock(); try { System.out.println(System.currentTimeMillis() + "," + Thread.currentThread().getName() + " start!"); try { condition.await(); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println(System.currentTimeMillis() + "," + Thread.currentThread().getName() + " 被唤醒!"); } finally { lock.unlock(); } }); t1.setName("t1"); t1.start(); //休眠5秒 TimeUnit.SECONDS.sleep(1); lock.lock(); try { condition.signal(); } finally { lock.unlock(); } System.out.println(System.currentTimeMillis() + ",condition.signal();执行完毕"); } }
运行结果:
1563594886532,condition.signal();执行完毕 1563594890532,t1 start!
输出上面2行以后,程序没法结束,代码结合输出能够看出signal()方法在await()方法以前执行的,最终t1线程没法被唤醒,致使程序没法结束。
关于Condition中方法使用总结:
IllegalMonitorStateException
异常关于Object和Condtion中线程等待和唤醒的局限性,有如下几点:
关于这2点,LockSupport都不须要,就能实现线程的等待和唤醒。下面咱们来讲一下LockSupport类。
LockSupport类能够阻塞当前线程以及唤醒指定被阻塞的线程。主要是经过park()和unpark(thread)方法来实现阻塞和唤醒线程的操做的。
每一个线程都有一个许可(permit),permit只有两个值1和0,默认是0。
- 当调用unpark(thread)方法,就会将thread线程的许可permit设置成1(注意屡次调用unpark方法,不会累加,permit值仍是1)。
- 当调用park()方法,若是当前线程的permit是1,那么将permit设置为0,并当即返回。若是当前线程的permit是0,那么当前线程就会阻塞,直到别的线程将当前线程的permit设置为1时,park方法会被唤醒,而后会将permit再次设置为0,并返回。
注意:由于permit默认是0,因此一开始调用park()方法,线程一定会被阻塞。调用unpark(thread)方法后,会自动唤醒thread线程,即park方法当即返回。
阻塞线程
void park():阻塞当前线程,若是调用unpark方法或者当前线程被中断,从能从park()方法中返回
void park(Object blocker):功能同方法1,入参增长一个Object对象,用来记录致使线程阻塞的阻塞对象,方便进行问题排查
void parkNanos(long nanos):阻塞当前线程,最长不超过nanos纳秒,增长了超时返回的特性
void parkNanos(Object blocker, long nanos):功能同方法3,入参增长一个Object对象,用来记录致使线程阻塞的阻塞对象,方便进行问题排查
void parkUntil(long deadline):阻塞当前线程,直到deadline,deadline是一个绝对时间,表示某个时间的毫秒格式
唤醒线程
主线程线程等待5秒以后,唤醒t1线程,代码以下:
package com.itsoku.chat10; import java.util.concurrent.TimeUnit; import java.util.concurrent.locks.Condition; import java.util.concurrent.locks.LockSupport; import java.util.concurrent.locks.ReentrantLock; /** * 微信公众号:javacode2018,获取年薪50万课程 */ public class Demo7 { public static void main(String[] args) throws InterruptedException { Thread t1 = new Thread(() -> { System.out.println(System.currentTimeMillis() + "," + Thread.currentThread().getName() + " start!"); LockSupport.park(); System.out.println(System.currentTimeMillis() + "," + Thread.currentThread().getName() + " 被唤醒!"); }); t1.setName("t1"); t1.start(); //休眠5秒 TimeUnit.SECONDS.sleep(5); LockSupport.unpark(t1); System.out.println(System.currentTimeMillis() + ",LockSupport.unpark();执行完毕"); } }
输出:
1563597664321,t1 start! 1563597669323,LockSupport.unpark();执行完毕 1563597669323,t1 被唤醒!
t1中调用LockSupport.park();
让当前线程t1等待,主线程休眠了5秒以后,调用LockSupport.unpark(t1);
将t1线程唤醒,输出结果中一、3行结果相差5秒左右,说明t1线程等待5秒以后,被唤醒了。
LockSupport.park();
无参数,内部直接会让当前线程处于等待中;unpark方法传递了一个线程对象做为参数,表示将对应的线程唤醒。
唤醒方法放在等待方法以前执行,看一下线程是否可以被唤醒呢?代码以下:
package com.itsoku.chat10; import java.util.concurrent.TimeUnit; import java.util.concurrent.locks.LockSupport; /** * 微信公众号:javacode2018,获取年薪50万课程 */ public class Demo8 { public static void main(String[] args) throws InterruptedException { Thread t1 = new Thread(() -> { try { TimeUnit.SECONDS.sleep(5); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println(System.currentTimeMillis() + "," + Thread.currentThread().getName() + " start!"); LockSupport.park(); System.out.println(System.currentTimeMillis() + "," + Thread.currentThread().getName() + " 被唤醒!"); }); t1.setName("t1"); t1.start(); //休眠1秒 TimeUnit.SECONDS.sleep(1); LockSupport.unpark(t1); System.out.println(System.currentTimeMillis() + ",LockSupport.unpark();执行完毕"); } }
输出:
1563597994295,LockSupport.unpark();执行完毕 1563597998296,t1 start! 1563597998296,t1 被唤醒!
代码中启动t1线程,t1线程内部休眠了5秒,而后主线程休眠1秒以后,调用了LockSupport.unpark(t1);
唤醒线程t1,此时LockSupport.park();
方法还未执行,说明唤醒方法在等待方法以前执行的;输出结果中二、3行结果时间同样,表示LockSupport.park();
没有阻塞了,是当即返回的。
说明:唤醒方法在等待方法以前执行,线程也可以被唤醒,这点是另外2中方法没法作到的。Object和Condition中的唤醒必须在等待以后调用,线程才能被唤醒。而LockSupport中,唤醒的方法不论是在等待以前仍是在等待以后调用,线程都可以被唤醒。
park()让线程等待以后,是否可以响应线程中断?代码以下:
package com.itsoku.chat10; import java.util.concurrent.TimeUnit; import java.util.concurrent.locks.LockSupport; /** * 微信公众号:javacode2018,获取年薪50万课程 */ public class Demo9 { public static void main(String[] args) throws InterruptedException { Thread t1 = new Thread(() -> { System.out.println(System.currentTimeMillis() + "," + Thread.currentThread().getName() + " start!"); System.out.println(Thread.currentThread().getName() + ",park()以前中断标志:" + Thread.currentThread().isInterrupted()); LockSupport.park(); System.out.println(Thread.currentThread().getName() + ",park()以后中断标志:" + Thread.currentThread().isInterrupted()); System.out.println(System.currentTimeMillis() + "," + Thread.currentThread().getName() + " 被唤醒!"); }); t1.setName("t1"); t1.start(); //休眠5秒 TimeUnit.SECONDS.sleep(5); t1.interrupt(); } }
输出:
1563598536736,t1 start! t1,park()以前中断标志:false t1,park()以后中断标志:true 1563598541736,t1 被唤醒!
t1线程中调用了park()方法让线程等待,主线程休眠了5秒以后,调用t1.interrupt();
给线程t1发送中断信号,而后线程t1从等待中被唤醒了,输出结果中的一、4行结果相差5秒左右,恰好是主线程休眠了5秒以后将t1唤醒了。结论:park方法能够相应线程中断。
LockSupport.park方法让线程等待以后,唤醒方式有2种:
interrupt()
方法,给等待的线程发送中断信号,能够唤醒线程LockSupport有几个阻塞放有一个blocker参数,这个参数什么意思,上一个实例代码,你们一看就懂了:
package com.itsoku.chat10; import java.util.concurrent.TimeUnit; import java.util.concurrent.locks.LockSupport; /** * 微信公众号:javacode2018,获取年薪50万课程 */ public class Demo10 { static class BlockerDemo { } public static void main(String[] args) throws InterruptedException { Thread t1 = new Thread(() -> { LockSupport.park(); }); t1.setName("t1"); t1.start(); Thread t2 = new Thread(() -> { LockSupport.park(new BlockerDemo()); }); t2.setName("t2"); t2.start(); } }
运行上面代码,而后用jstack查看一下线程的堆栈信息:
"t2" #13 prio=5 os_prio=0 tid=0x00000000293ea800 nid=0x91e0 waiting on condition [0x0000000029c3f000] java.lang.Thread.State: WAITING (parking) at sun.misc.Unsafe.park(Native Method) - parking to wait for <0x00000007180bfeb0> (a com.itsoku.chat10.Demo10$BlockerDemo) at java.util.concurrent.locks.LockSupport.park(LockSupport.java:175) at com.itsoku.chat10.Demo10.lambda$main$1(Demo10.java:22) at com.itsoku.chat10.Demo10$$Lambda$2/824909230.run(Unknown Source) at java.lang.Thread.run(Thread.java:745) "t1" #12 prio=5 os_prio=0 tid=0x00000000293ea000 nid=0x9d4 waiting on condition [0x0000000029b3f000] java.lang.Thread.State: WAITING (parking) at sun.misc.Unsafe.park(Native Method) at java.util.concurrent.locks.LockSupport.park(LockSupport.java:304) at com.itsoku.chat10.Demo10.lambda$main$0(Demo10.java:16) at com.itsoku.chat10.Demo10$$Lambda$1/1389133897.run(Unknown Source) at java.lang.Thread.run(Thread.java:745)
代码中,线程t1和t2的不一样点是,t2中调用park方法传入了一个BlockerDemo对象,从上面的线程堆栈信息中,发现t2线程的堆栈信息中多了一行- parking to wait for <0x00000007180bfeb0> (a com.itsoku.chat10.Demo10$BlockerDemo)
,恰好是传入的BlockerDemo对象,park传入的这个参数可让咱们在线程堆栈信息中方便排查问题,其余暂无他用。
LockSupport的其余等待方法,包含有超时时间了,过了超时时间,等待方法会自动返回,让线程继续运行,这些方法在此就不提供示例了,有兴趣的朋友能够本身动动手,练一练。
到目前为止,已经说了3种让线程等待和唤醒的方法了
3种方式对比:
Object | Condtion | LockSupport | |
---|---|---|---|
前置条件 | 须要在synchronized中运行 | 须要先获取Lock的锁 | 无 |
无限等待 | 支持 | 支持 | 支持 |
超时等待 | 支持 | 支持 | 支持 |
等待到未来某个时间返回 | 不支持 | 支持 | 支持 |
等待状态中释放锁 | 会释放 | 会释放 | 不会释放 |
唤醒方法先于等待方法执行,可否唤醒线程 | 否 | 否 | 能够 |
是否能响应线程中断 | 是 | 是 | 是 |
线程中断是否会清除中断标志 | 是 | 是 | 否 |
是否支持等待状态中不响应中断 | 不支持 | 支持 | 不支持 |
java高并发系列连载中,总计估计会有四五十篇文章,能够关注公众号:javacode2018,获取最新文章。