3.系统学习JavaEE:多线程
创始人
2025-05-29 02:52:58
0

作者:爱塔居

专栏:JavaEE

作者介绍:大三学生,希望跟大家一起进步

文章目录

目录

文章目录

上章节回顾

一、中断线程

1.1 给线程中设定一个结束标志位。

 1.2 interrupt()

1.3为什么sleep要清空标志位呢?

二、等待一个线程—join()

三、线程的状态

 四、多线程带来的风险-线程安全(重点)


上章节回顾

1.线程的概念

线程之间资源共享,开销小,效率高。

2.进程和线程的区别

进程包含线程。(盖棺定论)一个进程里面至少有一个线程。进程是操作系统资源分配的基本单位。线程是操作系统进行调度执行的基本单位。

3.java代码如何创建线程

1)继承Thread,重写run

2)实现Runnable,重写run

3)继承Thread,匿名内部类

4)实现Runnable,匿名内部类

5)lambda表达式(推荐使用)

4.Thread常见属性

start方法:真正从系统这里,创建一个线程,新的线程将会执行run方法。

run方法表示了线程的入口方法是啥。(线程启动起来,要执行哪些逻辑。不是让程序员调用,要交给系统去自动调用)

一、中断线程

中断一个线程

中断这里就是字面意思,就是让一个线程停下来。线程的终止,本质上说,办法只有一种,就是让该线程的入口方法执行完毕。

1.1 给线程中设定一个结束标志位。

把循环条件用一个变量来控制:

package threading;
public class ThreadDemo6 {public static  boolean isQuit=false;public static void main(String[] args) {Thread t=new Thread(()-> {while (!isQuit){System.out.println("hello t");try{Thread.sleep(1000);//休息1秒}catch (InterruptedException e){e.printStackTrace();}}System.out.println("t 线程终止");});t.start();//在主线程中,修改isQuittry {Thread.sleep(3000);//主线程休息三秒}catch (InterruptedException e){e.printStackTrace();}isQuit=true;//将isQuit值改成true}
}

 如果把isQuit从成员变量改成局部遍历main。该代码能否正常工作?

不能。因为变量捕获。

lambda表达式能否访问外面的局部变量?可以,变量捕获语法规则。java要求变量捕获,捕获的变量必须是final或者实际final(变量没有用final修饰,但是代码中没有做出修改)。

 1.2 interrupt()

package threading;
public class ThreadDemo7 {public static void main(String[] args) {Thread t=new Thread(()->{//currentThread是获取当前线程实例//此处currentThread得到的对象就是t//isInterrupted是t对象里自带的一个标志位while (!Thread.currentThread().isInterrupted()){System.out.println("hello t");try {Thread.sleep(1000);}catch (InterruptedException e){e.printStackTrace();//打印出当前异常位置的调用栈}}});t.start();try{Thread.sleep(3000);}catch (InterruptedException e){e.printStackTrace();}//把t内部的标志位设置成true//如果该进程阻塞中,就会把阻塞状态唤醒,通过抛出异常的方式,让sleep结束。t.interrupt();//只执行一次}
}

 注意:当sleep被唤醒的时候,sleep会自动地把isInterrupted标志位给清空(true->fasle)

调用interrupt,设置了中断标志位,sleep第一次执行,清空了标志位,变成false,并抛出异常。sleep第二次执行,没有这个中断的标志位了。主线程并非是循环反复设置,而是只执行一次。

如果设置interrupt的时候,恰好,sleep刚醒。着生活,执行到下一轮的循环的条件后,也就直接结束了。不过这种概率很低,毕竟sleep的时间已经占据了整个循环体的99.999999999%的时间了。

 多线程的代码执行顺序不是“从上到下”,而是每个线程独立执行的。

多线程的调式是比较麻烦的。因为调试运行的效果和实际运行的效果可能差异很大。比如你给t线程打断点了,此时main线程仍然正常执行。

针对多线程的调试,最主要的方法是打印日志。

package threading;
public class ThreadDemo7 {public static void main(String[] args) {Thread t=new Thread(()->{//currentThread是获取当前线程实例//此处currentThread得到的对象就是t//isInterrupted是t对象里自带的一个标志位while (!Thread.currentThread().isInterrupted()){System.out.println("hello t");try {Thread.sleep(1000);}catch (InterruptedException e){e.printStackTrace();//打印出当前异常位置的调用栈break;}}});t.start();try{Thread.sleep(3000);}catch (InterruptedException e){e.printStackTrace();}//把t内部的标志位设置成true//如果该进程阻塞中,就会把阻塞状态唤醒,通过抛出异常的方式,让sleep结束。t.interrupt();}
}

当我们加上break,这个代码就能正常结束了。

1.3为什么sleep要清空标志位呢?

清空标志位的目的是让线程本身能够对于线程何时结束,有一个更明确的控制。

当前的interrupt方法效果,并不是让线程立即结束,而是告诉他,你该结束了,至于他是否真的要结束,立即结束还是等会结束,都是代码来灵活控制的。

 比如,你在打游戏的时候,妈妈让你帮忙到楼下扔个垃圾。

这时候,你要选择放下手机,立马去丢垃圾,还是跟妈妈说等会再去,还是直接说,我要打游戏,不丢垃圾呢?

 很明显,我们会先试试跟妈妈商量,“妈,打完这把,我就去丢垃圾。马上就打完了。”如果妈妈不同意,我们就会放下手机,赶紧下楼丢垃圾。

如果,我们在上网课,认真学习的时候,妈妈进来说,你去楼下丢个垃圾。

我们坚决跟妈妈说:“不。我正忙着听课呢。”

interrupt只是建议,不是命令。

 为什么java不强制设定为“命令结束”的操作呢?只要调用interru就立即结束呢?

因为设定成这种,非常不友好。

我们在打游戏的时候,说去丢垃圾,还能理解。毕竟,打游戏是一种娱乐。可是,正在认真学习,你二话不说,让我立马去丢垃圾,就很不合理了。

线程t何时结束,一定是t自己最清楚。交给t自己决定,就比较好。

二、等待一个线程—join()

线程之间是并发执行的。操作系统对于线程的调度是无序的,无法判定两个线程谁先执行结束,谁后执行结束。

package threading;public class ThreadDemo8 {public static void main(String[] args) {Thread t=new Thread(()->{System.out.println("hello t");});t.start();System.out.println("hello main");}
}

我们先输出hello main 还是输出hello t是无法确定的。这个代码实际执行的时候,大概率先输出hello main。毕竟创建线程,是需要开销的。不过不排除特定情况。

我们是不喜欢不确定的,会出概率性的bug,很难受。有的时候,我们就要明确规定线程的结束顺序。

我们可以使用线程等待来实现。

package threading;public class ThreadDemo8 {public static void main(String[] args) throws InterruptedException{Thread t=new Thread(()->{System.out.println("hello t");});t.start();t.join();System.out.println("hello main");}
}

 t.join()是在main线程中,调用t.join。意思是让main线程等待t先结束,再往下执行。

如果是在t1线程中,调用t2.join,就是让t1线程等待t2先结束。

在t。join执行的时候,如果t线程还没有结束,main线程就会阻塞等待。阻塞就是代码走到这一行就停下了,当前线程暂时不参与cpu的调度执行。

如果只有两个线程,main等待t,如果t短时间不结束,那是不是就不算并发了?

main阻塞了,不参加cpu调度了,此时就只有t去执行,单个线程确实也谈不上并发。

 1.main线程调用t.join的时候,如果t还在运行,此时main线程阻塞,知道t执行完毕(t的run执行完了),main才从阻塞中解除,才继续执行。

2.main线程调用t.join的时候,如果t已经结束了,此时join不会阻塞,就会立即往下执行。

这两种情况都能保证t是先结束的。

join还有一个版本,可以填写一个参数,作为“超时时间”,等待的最大时间。

join的无参数版本,效果就是“死等”,不见不散。

join的有参数版本,则是执行最大超时时间,如果等待的时间到了上限,还没有等到,咱就不等了。

会不会有两个线程互相等待的情况

会有的。那种情况称为死锁,是bug。比如家钥匙锁车里了,车钥匙锁家里了。

三、线程的状态

操作系统里的线程,自身是有一个状态的。但是Java Thread是对系统线程的封装,把这里的状态又进一步地精细化了。

NEW 系统中地线程还没创建出来呢,只是有一个Thread对象。

package threading;public class ThreadDemo9 {public static void main(String[] args) {Thread t=new Thread(()->{System.out.println("hello t");});System.out.println(t.getState());t.start();}
}

输出结果:

TERMINATED 系统中的线程已经执行完了,Thread对象还在。

package threading;public class ThreadDemo9 {public static void main(String[] args)throws InterruptedException {Thread t=new Thread(()->{System.out.println("hello t");});System.out.println(t.getState());t.start();Thread.sleep(2000);System.out.println(t.getState());}
}

RUNNABLE 就绪状态 

1)正在CPU上运行。

2)准备好随时可以去CPU上执行。

package threading;public class ThreadDemo9 {public static void main(String[] args)throws InterruptedException {Thread t=new Thread(()->{while (true){// System.out.println("hello t");}});System.out.println(t.getState());t.start();Thread.sleep(2000);System.out.println(t.getState());}
}

 

 TIMED_WAITNG 指定时间等待.sleep方法。

package threading;public class ThreadDemo9 {public static void main(String[] args)throws InterruptedException {Thread t=new Thread(()->{while (true){try {Thread.sleep(1000);}catch (InterruptedException e){e.printStackTrace();}}});System.out.println(t.getState());t.start();Thread.sleep(2000);System.out.println(t.getState());}
}


 BLOCKED 表示等待锁出现的状态

WAITING 使用wait方法出现的状态

 

 一条主线,三个支线。理解线程状态,意义就是让我们能够更好地进行多线程代码的调试。

 四、多线程带来的风险-线程安全(重点)

本章的重点,难点,考点。‘某个代码,在多线程环境下执行,会出bug=》线程不安全

举例子:

创建两个线程,各执行5w次自增。正常情况,结果是10w。

package threading;
//线程不安全
class Counter{private int count=0;public void add(){count++;}public int get(){return count;}
}
public class ThreadDemo10 {public static void main(String[] args) throws InterruptedException{Counter counter=new Counter();//搞两个线程,两个线程分别对这个counter自增5w次Thread t1=new Thread(()->{for (int i = 0; i <50000 ; i++) {counter.add();}});t1.start();Thread t2=new Thread(()-> {for (int i = 0; i < 50000; i++) {counter.add();}});t2.start();t1.join();t2.join();System.out.println(counter.get());}
}

输出结果:

 

我们会发现,不仅不是10w次,而且每次结果不一样。 

 解释一下为啥会出现这种情况,和线程的调度随机性密切相关

count++操作,本质上是三个cpu指令的构成

1.load,把内存中的数据读取到cpu寄存器中

2.add,就是把寄存器中的值,进行+1运算

3.save,把寄存器中的值写回到内存中

 由于多线程调度顺序是不确定的。实际执行过程中,这俩线程的++操作实际的指令排序顺序有很多种可能~

可能是这个样子:

也有可能是这样:

 还有这种可能:

 还有这种可能:

 也有这种可能:

此处两个线程的指令的排序顺序有着很多种排列情况。

不同的排列顺序,结果是截然不同的。

试着分析一下:

t1和t2是两个线程,可能是运行在不同的cpu核心上,也可能是运行在同一个cpu核心上(但是是分时复用,并发)这两种情况最终结果一致。

 

 这个结果是正常的。

我们再试着分析这种情况:

 

 此时我们发现,两个线程自增两次,最后结果是1.其中一次自增的结果,被另一次覆盖了。

 由于当前这两个线程调度顺序是无序的,有多少次是“顺序执行”,有多少次是“交错执行”是不知道的。

所以出现bug之后,得到的结果一定是<=10w。

结果是一定>=5w吗?

完全有可能小于5w,只是概率会小一些。

 

相关内容

热门资讯

银价推涨光伏组件报价,下游企业... 来源:第一财经 受成本端银价上涨影响,本周光伏组件价格再次上调。据行业机构Infolink Cons...
黄金史诗级暴跌,原因可能与一纸... 当地时间1月30日,随着美联储前理事凯文·沃什(Kevin Warsh)正式被美国总统特朗普提名为下...
深圳国资七亿下场扫货白石洲? 来源:市场资讯 (来源:深圳房产在线) 最近看到,近日一则消息引发关注,就是今年1月发生一宗白石洲大...
国投智能2025业绩承压 AI... 来源:财联社 财联社1月30日讯(记者 方彦博)2025年,AI应用的商业化落地是众多AI企业面临的...
原创 男... 在爱情的海洋中,星座的波涛有时能揭示出隐藏的情感暗流。当男人在愤怒的风暴中显露出四种迹象时,或许他并...
农业银行董事长谷澍会见英格兰银... 来源:市场资讯 来源:中国农业银行 1月29日,农业银行董事长谷澍会见了英格兰银行副行长兼英国审慎监...
“易中天”,业绩大爆发!需求增... “易中天”2025年度业绩持续爆发! 1月30日晚间,中际旭创发布2025年度业绩预告,预计2025...
双平台战略提速:仙乐健康谋“A... 中国营养健康食品行业的龙头企业仙乐健康,在1月30日向市场投下了一枚重磅消息:公司已正式向香港联交所...
左季庆染指淳厚基金股权纷争为谁... 2026年1月6日,证监会一纸批复核准上海长宁国有资产经营投资有限公司(下称“长宁国资”)成为淳厚基...
上市即巅峰?拉芳家化首度亏损,... 为什么消费端对“拉芳”爱不起来了? 作者 | 方璐 编辑丨于婞 来源 | 野马财经 拉芳家化(603...
原创 黄... 1月31日晚间,英伟达CEO黄仁勋现身中国台湾台北市砖窑古早味怀旧餐厅,宴请了35位与英伟达合作的供...
山西太钢不锈钢股份有限公司 2... 来源:证券日报 证券代码:000825 证券简称:太钢不锈 公告编号:2026-001 本公司及董...
把自己的银行贷款出借给别人,有... 新京报讯(记者张静姝 通讯员邸越洋)因贷款出借后未被归还,原告牛女士将被告杨甲、杨乙诉至法院,要求二...
金价暴跌,刚买的金饰能退吗?有... 黄金价格大跌,多品牌设置退货手续费。 在过去两三天,现货黄金价格经历了“过山车”般的行情,受金价下跌...
预计赚超2500万!“豆腐大王... 图片来源:图虫创意 在经历了一年亏损后,“豆腐大王”祖名股份(003030.SZ)成功实现扭亏为盈。...
特朗普提名“自己人”沃什执掌美... 据新华社报道,当地时间1月30日,美国总统特朗普通过社交媒体宣布,提名美国联邦储备委员会前理事凯文·...
爱芯元智将上市:连年大额亏损,... 撰稿|多客 来源|贝多商业&贝多财经 1月30日,爱芯元智半导体股份有限公司(下称“爱芯元智”,HK...
一夜之间,10只A股拉响警报:... 【导读】深康佳A等10家公司昨夜拉响退市警报 中国基金报记者 夏天 1月30日晚间,A股市场迎来一波...
谁在操控淳厚基金?左季庆为谁趟... 2026年1月6日,证监会一纸批复核准上海长宁国有资产经营投资有限公司(下称“长宁国资”)成为淳厚基...
工商银行党委副书记、行长刘珺会... 人民财讯1月31日电,1月29日,工商银行党委副书记、行长刘珺会见来访的上海电气集团党委书记、董事长...