下面的代码执行,打印 r 是什么?
static int r = 0;public static void main(String[] args) throws InterruptedException { test1();}private static void test1() throws InterruptedException { log.debug("开始"); Thread t1 = new Thread(() -> { log.debug("开始"); sleep(1); log.debug("结束"); r = 10; }); t1.start(); log.debug("结果为:{}", r); log.debug("结束");}
分析
(资料图)
因为主线程和线程 t1 是并行执行的,t1 线程需要 1 秒之后才能算出 r=10
而主线程一开始就要打印 r 的结果,所以只能打印出 r=0
解决方法
用 主线程sleep 行不行?为什么? 这种方式不推荐,因为不清楚t1线程执行具体的时间
用 join,加在 t1.start()
之后即可,主线程执行到t1.join()时会等待t1线程结束
以调用方角度来讲,如果
需要等待结果返回,才能继续运行就是同步
不需要等待结果返回,就能继续运行就是异步
1.2 等待多个结果问,下面代码 cost 大约多少秒?
static int r1 = 0;static int r2 = 0;public static void main(String[] args) throws InterruptedException { test2();}private static void test2() throws InterruptedException { Thread t1 = new Thread(() -> { sleep(1); r1 = 10; }); Thread t2 = new Thread(() -> { sleep(2); r2 = 20; }); long start = System.currentTimeMillis(); t1.start(); t2.start(); t1.join(); t2.join(); long end = System.currentTimeMillis(); log.debug("r1: {} r2: {} cost: {}", r1, r2, end - start);}
分析如下
第一个 join:等待 t1 时, t2 并没有停止, 而在运行
第二个 join:1s 后, 执行到此, t2 也运行了 1s, 因此也只需再等待 1s
如果颠倒两个 join 呢?
最终都是输出
20:45:43.239 [main] c.TestJoin - r1: 10 r2: 20 cost: 2005
1.3 有时效的 join等够时间
static int r1 = 0;static int r2 = 0;public static void main(String[] args) throws InterruptedException { test3();}public static void test3() throws InterruptedException { Thread t1 = new Thread(() -> { sleep(1); r1 = 10; }); long start = System.currentTimeMillis(); t1.start(); // 线程执行结束会导致 join 结束 t1.join(1500); long end = System.currentTimeMillis(); log.debug("r1: {} r2: {} cost: {}", r1, r2, end - start);}
输出
20:48:01.320 [main] c.TestJoin - r1: 10 r2: 0 cost: 1010
没等够时间
static int r1 = 0;static int r2 = 0;public static void main(String[] args) throws InterruptedException { test3();}public static void test3() throws InterruptedException { Thread t1 = new Thread(() -> { sleep(2); r1 = 10; }); long start = System.currentTimeMillis(); t1.start(); // 线程执行结束会导致 join 结束 t1.join(1500); long end = System.currentTimeMillis(); log.debug("r1: {} r2: {} cost: {}", r1, r2, end - start);}
输出
20:52:15.623 [main] c.TestJoin - r1: 0 r2: 0 cost: 1502
2、interrupt 方法详解其主要作用是打断 sleep,wait,join 的线程
这几个方法都会让线程进入阻塞状态
打断 sleep 的线程, 会清空打断状态,以 sleep 为例
private static void test1() throws InterruptedException { Thread t1 = new Thread(()->{ sleep(1); }, "t1"); t1.start(); sleep(0.5); t1.interrupt(); log.debug(" 打断状态: {}", t1.isInterrupted());}
输出
java.lang.InterruptedException: sleep interrupted at java.lang.Thread.sleep(Native Method) at java.lang.Thread.sleep(Thread.java:340) at java.util.concurrent.TimeUnit.sleep(TimeUnit.java:386) at cn.itcast.n2.util.Sleeper.sleep(Sleeper.java:8) at cn.itcast.n4.TestInterrupt.lambda$test1$3(TestInterrupt.java:59) at java.lang.Thread.run(Thread.java:745)21:18:10.374 [main] c.TestInterrupt - 打断状态: false
正常运行状态的如果打断,那么打断标记为true,如果是阻塞状态被打断,那么其打断状态为false。
2.1 打断正常运行的线程打断正常运行的线程, 不会清空打断状态
private static void test2() throws InterruptedException { Thread t2 = new Thread(()->{ while(true) { Thread current = Thread.currentThread(); boolean interrupted = current.isInterrupted(); if(interrupted) { log.debug(" 打断状态: {}", interrupted); break; } } }, "t2"); t2.start(); sleep(0.5); t2.interrupt();}
输出
20:57:37.964 [t2] c.TestInterrupt - 打断状态: true
注意:这个打断标记只是一个标记信号,并不会结束线程的执行,一般是根据这个标记信号来决定是否结束当前线程。
2.2 采用两阶段终止线程,避免stop停止在一个线程 T1 中如何“优雅”终止线程 T2?这里的【优雅】指的是给 T2 一个料理后事的机会。
错误思路
使用线程对象的 stop() 方法停止线程
stop 方法会真正杀死线程,如果这时线程锁住了共享资源,那么当它被杀死后就再也没有机会释放锁, 其它线程将永远无法获取锁
使用 System.exit(int) 方法停止线程
目的仅是停止一个线程,但这种做法会让整个程序都停止
两阶段终止模式
2.2.1 利用 isInterruptedinterrupt 可以打断正在执行的线程,无论这个线程是在 sleep,wait,还是正常运行
class TPTInterrupt { private Thread thread; public void start(){ thread = new Thread(() -> { while(true) { Thread current = Thread.currentThread(); if(current.isInterrupted()) { log.debug("料理后事"); break; } try { Thread.sleep(1000); log.debug("将结果保存"); } catch (InterruptedException e) { current.interrupt(); // 设置为true } // 执行监控操作 } },"监控线程"); thread.start(); } public void stop() { thread.interrupt(); }}
调用
TPTInterrupt t = new TPTInterrupt();t.start();Thread.sleep(3500);log.debug("stop");t.stop();
结果
11:49:42.915 c.TwoPhaseTermination [监控线程] - 将结果保存11:49:43.919 c.TwoPhaseTermination [监控线程] - 将结果保存11:49:44.919 c.TwoPhaseTermination [监控线程] - 将结果保存11:49:45.413 c.TestTwoPhaseTermination [main] - stop 11:49:45.413 c.TwoPhaseTermination [监控线程] - 料理后事
2.2.2 利用停止标记// 停止标记用 volatile 是为了保证该变量在多个线程之间的可见性// 我们的例子中,即主线程把它修改为 true 对 t1 线程可见class TPTVolatile { private Thread thread; private volatile boolean stop = false; public void start(){ thread = new Thread(() -> { while(true) { Thread current = Thread.currentThread(); if(stop) { log.debug("料理后事"); break; } try { Thread.sleep(1000); log.debug("将结果保存"); } catch (InterruptedException e) { } // 执行监控操作 } },"监控线程"); thread.start(); } public void stop() { stop = true; thread.interrupt(); }}
调用
TPTVolatile t = new TPTVolatile();t.start();Thread.sleep(3500);log.debug("stop");t.stop();
结果
11:54:52.003 c.TPTVolatile [监控线程] - 将结果保存11:54:53.006 c.TPTVolatile [监控线程] - 将结果保存11:54:54.007 c.TPTVolatile [监控线程] - 将结果保存11:54:54.502 c.TestTwoPhaseTermination [main] - stop 11:54:54.502 c.TPTVolatile [监控线程] - 料理后事
2.3 打断处于park状态线程park, 进入WAITING状态,对比wait不需要获得锁就可以让线程WAITING,通过unpark唤醒
打断 处于park状态 线程, 不会清空打断状态
private static void test3() throws InterruptedException { Thread t1 = new Thread(() -> { log.debug("park..."); LockSupport.park(); log.debug("unpark..."); log.debug("打断状态:{}", Thread.currentThread().isInterrupted()); }, "t1"); t1.start(); sleep(1); t1.interrupt();}
输出
21:11:52.795 [t1] c.TestInterrupt - park...21:11:53.295 [t1] c.TestInterrupt - unpark...21:11:53.295 [t1] c.TestInterrupt - 打断状态:true
如果打断标记已经是 true, 则 park 会失效
private static void test4() { Thread t1 = new Thread(() -> { for (int i = 0; i < 5; i++) { log.debug("park..."); LockSupport.park(); log.debug("打断状态:{}", Thread.currentThread().isInterrupted()); } }); t1.start(); sleep(1); t1.interrupt();}
输出
21:13:48.783 [Thread-0] c.TestInterrupt - park...21:13:49.809 [Thread-0] c.TestInterrupt - 打断状态:true21:13:49.812 [Thread-0] c.TestInterrupt - park...21:13:49.813 [Thread-0] c.TestInterrupt - 打断状态:true21:13:49.813 [Thread-0] c.TestInterrupt - park...21:13:49.813 [Thread-0] c.TestInterrupt - 打断状态:true21:13:49.813 [Thread-0] c.TestInterrupt - park...21:13:49.813 [Thread-0] c.TestInterrupt - 打断状态:true21:13:49.813 [Thread-0] c.TestInterrupt - park...21:13:49.813 [Thread-0] c.TestInterrupt - 打断状态:true
3、不推荐的方法提示
可以使用
Thread.interrupted()
清除打断状态
还有一些不推荐使用的方法,这些方法已过时,容易破坏同步代码块,造成线程死锁
方法名 | static | 功能说明 |
---|---|---|
stop() | 停止线程运行 | |
suspend() | 挂起(暂停)线程运行 | |
resume() | 恢复线程运行 |
标签:
1、join方法详解1 1为什么需要join?下面的代码执行,打印r是什么?staticintr=0;publicstaticvoidmain(Str
是什么让国家一级演员,越剧表演艺术家何赛飞当众发飙?热搜整整挂了两天。事情缘起于一场戏曲大赛,该大赛
来为大家解答以上问题,药店软件,nokiae66软件很多人还不知道,现在让我们一起来看看吧!1、诺基亚品牌手
今日市场全面开花,创业板指数涨超1%领涨两市,成交量有所放出,MACD指标低位即将二次金叉,K线呈现“两阳
目录问题缘由背后原理C 代码示例总结问题缘由由于公司需求,需要读取游戏Redis数据做内外网数据迁移,没
可以。牛尾200克、西瓜酱10克、黄花菜100克、海带100克、木耳20克、腐竹50克、葱姜、胡椒粉、料酒、酱油、
想必现在有很多小伙伴对于磨泥打蜡和抛光打蜡的区别是什么方面的知识都比较想要了解,那么今天小好小编就为
广西新闻网防城港6月1日讯(通讯员蒙丽燕)日前,为期一个多月的2023年“潮动三月三民族体育炫”防城港...
恭喜我们破厂的“自家孩子”洛特入选英格兰国家队,征战女足世界杯!冬窗离队的12年枪手乔丹·诺布斯也...
不过市场狂热的背后,一部分所谓的相关概念股到底有没有相关技术,是不是真正拥有“脑机接口”业务成为...
B站150万UP主赚到钱了!正式会员突破2亿
老公不向着自己,婆家无视她的付出,这也导致她对婆家人心存芥蒂,宋丹丹同时也演出了吴二琥的无奈与心酸。
美国主导的“印太经济框架”日前基本完成“提高供应链韧性与安全”的协议谈判。这份协议旨在建立“排除...
奥飞娱乐涨9 97%机构净买入5271万元
想必现在有很多小伙伴对于下面图中S8550作用是什么功率放大还是什么放大方面的知识都比较想要了解,那么今
音频解说一、贵州省遵义市天气预报1、湄潭县气象台2023年06月01日11时57分发布雷电黄色预警信号。2、预计未来3小
近日,有观众反映:自己花千元购买梁静茹上海演唱会的门票,到场后发现,视野被舞台四周的立柱遮挡,看歌手
中国青年报客户端北京5月31日电(中青报·中青网记者张均斌)萌宠互动、亲子运动、户外研学……“六一”...
津南区(不含海教园)户籍学生2023-2024学年秋季学期小学转学时间网上登记时间:2023年6月1日10:00-2023年6月3
律师你好,我想咨询下我朋友倒车不小心碰到了一位老人,导致盆骨骨折