最新要闻
- 当前头条:喜提安卓13!小米平板5/Pro 12.4推送MIUI 14开发版:支持光子引擎
- 每日热文:SSD/内存白菜价难持续:国产厂商被制裁 三星等大厂减产提价
- AMD RX7900被吐槽空气卡 溢价千元普遍:国内用户持币等 买它还是4080?
- 【世界播资讯】小米又一爆款诞生!小米净水器销量破500万台
- 热推荐:新冠后丧失嗅觉的关键原因找到了 科学家:长期失灵也能恢复
- 环球通讯!3199元起 爱奇艺奇遇MIX VR一体机发布:4K级3000吋巨幕 支持Steam串流
- 焦点快播:趁早加满!下轮国内油价上调几成定局
- 影驰发布全球第三款8GHz DDR5内存:如此"光污染" 绝了
- 焦点速讯:电解质水到底有没有用?我来告诉你
- 美国人预期寿命降至25年来最低:三大因素导致一半的死亡
- 11月智能手机销量出炉:小米大卖340万台 国产第一
- 今日最新!东京奥运会陷入丑闻:审计人员发现实际成本高出20%
- 经典大富翁游戏《富甲天下3》登陆Steam 2023年1月9日发售
- 全球速看:美国“毅力号”在火星上丢了个东西:意义重大
- 微软:这四款游戏大作被索尼永久禁止登陆Xbox平台
- 天天看热讯:本田底气十足 全新一代皓影上市:18.59万元起
手机
iphone11大小尺寸是多少?苹果iPhone11和iPhone13的区别是什么?
警方通报辅警执法直播中被撞飞:犯罪嫌疑人已投案
- iphone11大小尺寸是多少?苹果iPhone11和iPhone13的区别是什么?
- 警方通报辅警执法直播中被撞飞:犯罪嫌疑人已投案
- 男子被关545天申国赔:获赔18万多 驳回精神抚慰金
- 3天内26名本土感染者,辽宁确诊人数已超安徽
- 广西柳州一男子因纠纷杀害三人后自首
- 洱海坠机4名机组人员被批准为烈士 数千干部群众悼念
家电
02.关于线程你必须知道的8个问题(上)
大家好,我是王有志,欢迎来到《Java面试都问啥?》的第一篇技术文章。
【资料图】
这个系列会从Java部分开始,接着是MySQL和Redis的内容,同时会继续更新数据结构与算法的部分,这样在第一阶段,我们就完成了面试“三幻神”的挑战。
Java的部分从并发编程开始,接着是Java虚拟机,最后是集合框架。至于Java基础,因为大部分只是API的使用,所以只提供整理好的题目,而涉及到反射,动态代理等内容,会在集合框架完成后补充。
那么话不多说,我们直接开始吧。
并发编程都问啥?
每个模块开始时,我都会放出这一模块中知识点的统计数据,供大家参考。
统计中,我将并发编程分为了5个知识点:
- 线程基础:线程的基本概念,Thread类的使用等;
- 线程池:线程池的原理,线程池的使用等;
- synchronized:原理,锁升级,优化等;
- volatile:原理,指令重排,JMM相关等;
- ThreadLocal:原理,使用方法,内存泄漏等;
- JUC:Lock接口,并发容器,CAS,AQS等。
统计到并发编程关键词174次,线程出现37次,线程池出现22次,synchronized出现30次,volatile出现12次,ThreadLocal出现8次,JUC出现44次,剩余21次仅提到多线程/并发编程。
从图中看,ThreadLocal和volatile出现概率较低,但个人建议面试准备中,并发编程的部分要全量准备。
数据大家都看到了,接下来看看各大公司都会问哪些关于线程的问题。这部分题目主要收集自某准网面经,浅紫色底色的题目是我和小伙伴在面试过程遇到过的。
MarkDown的表格实在太丑了,偷个懒使用图片代替了,文末附上整理后Excel的获取方式。
关于线程你必须知道的8个问题
涉及到概念性的题目就不过多赘述了,这些可以通过百度百科获取到答案。在这里我挑选了8道比较有代表性的问题,和大家分享我的理解。
并发编程的3要素
并发编程的3要素:
- 原子性:操作不可分割,要么不间断的全部执行,要么全部不执行;
- 有序性:指程序按照代码的顺序结构执行;
- 可见性:当一个线程修改了共享变量后,其它线程也是立即可见的。
概念很简单,我们写一些代码展示下有序性和可见性的问题(原子性实在没有想到很好的例子,有没有小伙伴提供示例呢)。
有序性问题
public static class Singleton {private Singleton instance;public Singleton getInstance() { if (instance == null) { instance = new Singleton(); } return instance;} private Singleton() {}}
这是有序性问题的经典案例--未做同步控制的单例模式。当instance
还未初始化时,多个线程同时调用getInstance
方法,很容易出现其中一个线程获取到的instance
为NULL。
这里涉及Java创建对象的操作,CPU时间片分配的问题,解决它的办法也有很多,暂时按下不表,放到volatile
关键字的内容中详细解释。
可见性问题
private static boolean flag = true;public static void main(String[] args) throws InterruptedException { new Thread(() -> { while (flag) { } System.out.println("线程:" + Thread.currentThread().getName() + ",flag:" + flag); }, "block_thread").start(); TimeUnit.MICROSECONDS.sleep(500); new Thread(() -> { flag = false; System.out.println("线程:" + Thread.currentThread().getName() + ",flag:" + flag); }, "change_thread").start();}
很明显,在change_thread中修改了flag,并不会使block_thread得到解脱,这就是共享变量在线程间不可见的问题。
Java创建线程的方式
通常网上的资料会给出4种创建线程的方式:
- 继承Thread类
- 实现Runnable接口
- 实现Callable接口
- 通过线程池创建
先不评价这个答案的正确性,我们先来看看继承Thread
类,实现Runnable
接口和实现Callable
接口是如何使用的。
继承Thread类
public class ByThread {public static void main(String[] args) throws InterruptedException { System.out.println("main的线程:" + Thread.currentThread().getName()); MyThread myThread = new MyThread(); myThread.start();}static class MyThread extends Thread { @Override public void run() { System.out.println("MyThread的线程:" + Thread.currentThread().getName()); }}}
继承Thread
类要实现run
方法,用于完成业务逻辑,该方法来自于Runnable
接口。启动线程通过Thread.start
方法,方法内通过调用native方法start0
来启动线程。
实现Runnable接口
public class ByRunnable { public static void main(String[] args) { System.out.println("main的线程:" + Thread.currentThread().getName()); new Thread(new MyRunnable()).start(); } static class MyRunnable implements Runnable { @Override public void run() { System.out.println("MyRunnable的线程:" + Thread.currentThread().getName()); } }}
实现Runnable
接口同样要实现run
方法,启动线程依旧是通过Thread.start
方法。
实质上继承Thread
类和实现Runnable
接口没有差别,只不过是隔代实现run
方法还是直接实现run
方法。
实现Callable接口
public class ByCallable { public static void main(String[] args) throws ExecutionException, InterruptedException { System.out.println("main的线程:" + Thread.currentThread().getName()); Callable callable = new MyCallable(); FutureTask futureTask = new FutureTask<>(callable); new Thread(futureTask).start(); System.out.println("MyCallable的执行线程:" + futureTask.get()); } static class MyCallable implements Callable { @Override public String call() { System.out.println("MyCallable的线程:" + Thread.currentThread().getName()); return Thread.currentThread().getName(); } }}
实现Callable
接口看起来会复杂一些,但通过代码可以看出来,最终还是回归到Thread.start
方法,根据经验,这种方式是不是和Runnable
有关系?
另外,我们注意到这种方式中借助到了FutureTask
类,来看看FutureTask
的继承关系:
不出所料,FutureTask
同样要实现Runnable.run
方法,只不过这次由FutureTask
实现,FutureTask
在run
方法中调用Callable.call
方法来执行业务逻辑。
我们来回顾下这3种方式的特点,启动线程都是通过Thread.start
方法,start
方法的基本执行单位是Runnable
接口,它们直接的差异在于如何实现Runnable.run
方法。另一个差异就是Callable.call
方法是有返回值的,而Runnable.run
方法没有返回值。
使用线程池
public class ByThreadPool { public static void main(String[] args) { System.out.println("main的线程:" + Thread.currentThread().getName()); ExecutorService executorService = Executors.newSingleThreadExecutor(); Runnable runnable = new Runnable() { @Override public void run() { System.out.println("线程池的线程:" + Thread.currentThread().getName()); } }; executorService.execute(runnable); executorService.shutdown(); }}
使用线程池依旧离不开Runnable.run
方法,会不会和Callable
一样本质上还是Thread.start
?
如果不熟悉ThreadPoolExecutor
源码的话,可以采用断点的方式去跟踪源码,重点关注ThreadPoolExecutor.execute
和ThreadPoolExecutor.addWorker
两个方法。
我们可以在addWorker
方法中发现两行关键代码:
final Thread t = w.thread;t.start();
这证实了关于ThreadPoolExecutor
底层调用的猜想,最终依旧是通过Thread.start
方法启动。
回到最初的问题,Java有几种创建线程的方式?
如果从Java的层面来看,可以认为创建Thread
类的实例对象就完成了线程的创建,而调用Thread.start0
可以认为是操作系统层面的线程创建和启动。
至于网上说的4种创建线程的方式,个人认为将它们归类到线程中业务逻辑的实现方式更合理。
Java的线程状态
Java中定义了6种线程状态(与OS的线程状态有差别),线程状态的枚举类被定义为Thread
的内部类State
。
public enum State {NEW,RUNNABLE,BLOCKED,WAITING,TIMED_WAITING,TERMINATED;}
需要注意,Java中并未定义线程的RUNNING状态,而是通过RUNNABLE包含了RUNNABLE(可运行)和RUNNING(运行中)。
建议大家阅读源码中的注释,很清晰的解释了每个状态的场景。下面我还是通过几段代码展示线程的不同状态。
Tips:代码中出现的TimeUnit.MILLISECONDS.sleep
是为了确保线程已经进入期望的状态,如果不能很好的理解,文末附有Gitee地址,工程中的代码有注释。
常规状态的转换
这里指的是线程从创建后(NEW),到启动后(RUNNABLE),再到最后终止(TERMINATED)的一种无竞争的线程状态转换。
- NEW(新建):创建线程后尚未启动(未调用
start
方法); - RUNNABLE(可运行):可运行状态的线程在Java虚拟机中等待调度线程选中获取CPU时间片;
- TERMINATED(终止):线程执行结束。
写一段简单的代码来看下:
public class NormalStateTransition { public static void main(String[] args) throws InterruptedException { Thread thread = new Thread(() -> { try { TimeUnit.MILLISECONDS.sleep(1000); } catch (InterruptedException e) { throw new RuntimeException(e); } }); System.out.println("线程[" + thread.getName() + "]创建,状态:[" + thread.getState() + "]"); thread.start(); System.out.println("线程[" + thread.getName() + "]启动,状态:[" + thread.getState() + "]"); TimeUnit.SECONDS.sleep(2); System.out.println("线程[" + thread.getName() + "]结束,状态:[" + thread.getState() + "]"); }}
代码非常简单,这里就不再解释了。
常规状态的转换:
阻塞状态的转换
阻塞状态是一种“异常”的状态,通常是在等待资源。
BLOCKED(阻塞):等待监视器锁而阻塞的线程状态,处于阻塞状态的线程正在等待监视器锁进入同步的代码块/方法,或者在调用Object.wait
之后重新进入同步的代码块/方法。
再写一段代码:
public class BlockedStateTransition { public static void main(String[] args) throws InterruptedException { AtomicBoolean locker = new AtomicBoolean(false); new Thread(() -> { synchronized (locker) { try { TimeUnit.MILLISECONDS.sleep(3000); } catch (InterruptedException e) { e.printStackTrace(); } } }).start(); Thread t = new Thread(() -> { synchronized (locker) { try { TimeUnit.MILLISECONDS.sleep(500); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println("线程[" + Thread.currentThread().getName() + "]阻塞后,状态:[" + Thread.currentThread().getState() + "]"); } }); System.out.println("线程[" + t.getName() + "]创建,状态:[" + t.getState() + "]"); t.start(); System.out.println("线程[" + t.getName() + "]启动,状态:[" + t.getState() + "]"); System.out.println("线程[" + t.getName() + "]阻塞中,状态:[" + t.getState() + "]"); TimeUnit.MILLISECONDS.sleep(5000); System.out.println("线程[" + t.getName() + "]结束,状态:[" + t.getState() + "]"); }}
首先是匿名线程持有locker
,接着线程t启动,进入RUNNABLE状态,线程t尝试获取locker
,进入BLOCKED状态,等待后获取到locker
,进入RUNNABLE状态,最后执行结束,进入TERMINATED状态。
阻塞状态转换:
等待状态的转换
关于等待状态,Java源码的注释有详细描述如何进入等待状态,以及如何唤醒处于等待状态的线程:
Thread state for a waiting thread. A thread is in the waiting state due to calling one of the following methods:Object.wait with no timeoutThread.join with no timeoutLockSupport.parkA thread in the waiting state is waiting for another thread to perform a particular action. For example, a thread that has called Object.wait() on an object is waiting for another thread to call Object.notify() or Object.notifyAll() on that object. A thread that has called Thread.join() is waiting for a specified thread to terminate.
WAITING(等待):线程处于等待状态,处于等待状态的线程正在等待另一个线程执行的特定操作(通知或中断)。
再再写一段代码:
public class WaitingStateTransition { public static void main(String[] args) throws InterruptedException { AtomicBoolean locker = new AtomicBoolean(false); Thread t = new Thread(() -> { synchronized (locker) { try { TimeUnit.MILLISECONDS.sleep(100); locker.wait(); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println("线程[" + Thread.currentThread().getName() + "]唤醒,状态:[" + Thread.currentThread().getState() + "]"); } }); System.out.println("线程[" + t.getName() + "]创建,状态:[" + t.getState() + "]"); t.start(); System.out.println("线程[" + t.getName() + "]启动,状态:[" + t.getState() + "]"); TimeUnit.MILLISECONDS.sleep(150); System.out.println("线程[" + t.getName() + "]等待,状态:[" + t.getState() + "]"); new Thread(() -> { synchronized (locker) { locker.notify(); } }).start(); TimeUnit.MILLISECONDS.sleep(100); System.out.println("线程[" + t.getName() + "]结束,状态:[" + t.getState() + "]"); }}
线程t创建后,进入NEW状态,启动后,进入RUNNABLE状态,locker.wait
后,进入WAITING状态,匿名线程启动,locker.notify
后,唤醒线程t,进入RUNNABLE状态,最后线程执行结束,进入TERMINATED状态。
等待状态的转换:
限时等待状态的转换
Java源码的注释上,也很详细的解释了如何进入限时等待:
Thread state for a waiting thread with a specified waiting time. A thread is in the timed waiting state due to calling one of the following methods with a specified positive waiting time:Thread.sleepObject.wait with timeoutThread.join with timeoutLockSupport.parkNanosLockSupport.parkUntil
TIMED_WAITING(限时等待):线程处于限时等待状态,与等待状态不同的是,在指定时间后,线程会被自动唤醒。
Tips:也有翻译成超时等待的,但是我觉得不太合适。
再再再写一段代码:
public class TimedWaitingStateTransition { public static void main(String[] args) throws InterruptedException { Thread t = new Thread(() -> { try { TimeUnit.MILLISECONDS.sleep(100); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println("线程[" + Thread.currentThread().getName() + "]限时等待后,状态:[" + Thread.currentThread().getState() + "]"); }); System.out.println("线程[" + t.getName() + "]创建,状态:[" + t.getState() + "]"); t.start(); System.out.println("线程[" + t.getName() + "]启动,状态:[" + t.getState() + "]"); TimeUnit.MILLISECONDS.sleep(50); System.out.println("线程[" + t.getName() + "]限时等待中,状态:[" + t.getState() + "]"); TimeUnit.MILLISECONDS.sleep(100); System.out.println("线程[" + t.getName() + "]结束,状态:[" + t.getState() + "]"); }}
线程t创建后,进入NEW状态,启动后进入RUNNABLE状态,线程休眠100ms,进入TIMED_WAITING状态,休眠时间结束后,进入RUNNABLE状态,最后线程执行结束,进入TERMINATED状态。
限时等待状态的转换:
线程状态转换总结
上面我们通过4段代码了解了线程状态的转换,下面我们通过一张图来总结下线程的状态转换。
结语
今天分享了并发编程的统计数据,因此面试题目较少,不过还是希望对你有帮助。
下一篇内容是剩余的5个知识点(如果一篇能够写完的话):
- Thread类核心方法
- 同步与互斥
- Java线程调度方式
- 死锁的产生与解决
- 多线程的优点
本篇文章的代码仓库:
- 并发3要素问题
- 线程实现方式
- 线程状态转换
关注王有志,回复面试题合集获取整理后的面试题(正在同步更新,可以持续关注)。
好了,今天就到这里了,Bye~~
02.关于线程你必须知道的8个问题(上)
当前头条:喜提安卓13!小米平板5/Pro 12.4推送MIUI 14开发版:支持光子引擎
每日热文:SSD/内存白菜价难持续:国产厂商被制裁 三星等大厂减产提价
天天热点!我的2022年个人总结
环球焦点!FreeSWITCH学习笔记:EventSocket
AMD RX7900被吐槽空气卡 溢价千元普遍:国内用户持币等 买它还是4080?
【世界播资讯】小米又一爆款诞生!小米净水器销量破500万台
热推荐:新冠后丧失嗅觉的关键原因找到了 科学家:长期失灵也能恢复
环球通讯!3199元起 爱奇艺奇遇MIX VR一体机发布:4K级3000吋巨幕 支持Steam串流
焦点快播:趁早加满!下轮国内油价上调几成定局
影驰发布全球第三款8GHz DDR5内存:如此"光污染" 绝了
焦点速讯:电解质水到底有没有用?我来告诉你
美国人预期寿命降至25年来最低:三大因素导致一半的死亡
11月智能手机销量出炉:小米大卖340万台 国产第一
天天短讯!多方安全计算(6):MPC中场梳理
云原生时代,18 岁的 NGINX 过时了吗?
AcWing1131. 拯救大兵瑞恩
今日最新!东京奥运会陷入丑闻:审计人员发现实际成本高出20%
经典大富翁游戏《富甲天下3》登陆Steam 2023年1月9日发售
全球新资讯:Apache Log4j 远程代码执行漏洞(CVE-2021-44228、CVE-2021-45046)修复方案
全球速看:美国“毅力号”在火星上丢了个东西:意义重大
微动态丨vue3的setup函数的使用
微软:这四款游戏大作被索尼永久禁止登陆Xbox平台
天天看热讯:本田底气十足 全新一代皓影上市:18.59万元起
一个比一个经典!卡梅隆电影角色人气排名:终结者T-800第一
教你用JavaScript实现进度条
世界消息!Sentinel
【天天快播报】知名游戏在美区遭和谐:美女角色太性感
【天天速看料】千元档亮度天花板!哈趣K1 Pro投影仪评测:真1080P分辨率白天也清晰
猫是牛顿流体 还是非牛顿流体?中科院严肃科普
这也可以?20万法国人请愿重踢世界杯决赛 阿根廷赢的不光彩
AMD RX 7900 XT破发:10天便宜快400块
今日要闻!Shell脚本4
风暴袭击!美国多个州宣布进入紧急状态 道路能见度可能为零
快资讯:感受彼此体温 杰士邦超薄尊享30只礼盒装19.9元
世界资讯:乘联会喊话:千方百计增加居民收入 大家踊跃买汽车稳消费
【新要闻】【验证码逆向专栏】某验三代滑块验证码逆向分析
短讯!安全多方计算(5):隐私集合求交方案汇总分析
天天观速讯丨论文解读()《Detect Rumors in Microblog Posts for Low-Resource Domains via Adver
每日讯息!GitHub实用开源项目
环球报道:韩国载有216人客机飞行中出现异常:靠一台发动机平安降落
天天热文:特斯拉股价年内大跌60%!最大空头:明年可能会更惨
每日动态!还差14亿刀回本!《阿凡达2》全球票房破6亿美元 说中国影迷不感兴趣尚早
TCL华星13.3英寸定制全隐私屏研发成功!全屏防窥、防窃听
今日看点:二次伤害猛于虎 事故后驾驶员留在现场:半小时后被撞身亡
世界热议:上干货 | 园区智慧物联管理解决方案
Shell脚本3
全球信息:国产CPU力挺国产OS!x86兆芯加入deepin深度社区
焦点速讯:《炉石传说》国服停运倒计时!官方补偿来了:10个卡包你领吗?
车门都不给配 新款雪铁龙My ami Buggy官图发布:年满14就能开
全球热文:有了AMD RX7900、4090深受市场青睐:RTX 4080还一无是处?
比亚迪仰望来了!首发极具颠覆性技术
微头条丨AcWing341. 洛谷P1073, NOIP2009 最优贸易
百事通!面向对象与面向过程
全球速递!Flex布局总结
马斯克给全球车主发福利:每人可“白嫖”30天免费EAP试用服务
阵容十分豪华 2023最受期待的十大游戏来了:暗黑4位列第三
首发天玑8100:荣耀平板V8 Pro带来超级笔记 自动去广告
起点读书宣布百部经典作品限时免费:包括《诛仙》《红楼梦》等
世界热点!男子修车时发现4S店虚报维修定损金额 要求退一赔三胜诉
Codeforces 1630 E Expected Components 题解 (组合数学)
头条:Java基础项目:超市管理项目
每日动态!2023春运车票24日开售 除夕车票要等到1月7日
《妮姬》首月收入突破6.9亿!腾讯海外收入占比提升达12.5%
今日热门!超越电竞机!Redmi K60要榨干第二代骁龙8:画质、帧率、亮度三不降
每日精选:效果堪比镀铬 2.2万元的特斯拉Model Y新配色值不值?
环球观察:高可用 Canal集群 实操( 秒懂 + 史上最全)
微头条丨认证管理(锐捷业软篇)
Intel拆分GPU部门 一把手重回技术岗 累计亏损超20亿美金
天天通讯!iPhone 14 Pro爆出“闪线门” :屏幕出现诡异的绿色和黄色细横线
全球热点评!当ChatGPT遇上弱智吧:全程爆笑
夫妻的世界翻拍哪部电视剧?夫妻的世界最后结局是什么意思?
滕王阁为什么叫阁不叫楼?滕王阁为什么是三大名楼之首?
情非情砸车是第几集?情非情盖总和保姆的结局是什么?
小昭去波斯是哪一集?小昭去波斯后她母亲去哪儿了?
男人是大猪蹄子是什么意思?男人是大猪蹄子女人是什么?
排序算法模板(更新中)
当前速读:机器学习——果蔬分类
每日消息!性能超越电竞手机!Redmi K60 Pro综合跑分达135万
信息:千万别强忍 20岁小伙憋气压抑咳嗽导致昏厥
特斯拉今年股价累计暴跌超60%!马斯克透露大跌原因
收购动视暴雪遇阻 微软哭弱:根本打不过索尼、任天堂
到手9袋!良品铺子坚果礼盒1440 仅44元包邮
每日讯息!教你用JavaScript实现背景图像滑动
户外运动有哪些项目?户外运动品牌排行榜
什么鱼营养价值最高?什么鱼只会逆流而上?
金木水火土命怎么算出来的?金木水火土哪个腿长?
玉面小飞龙是什么意思?玉面小飞龙出自哪里?
Redmi K60系列上架:三颗口碑最好的芯片都拿到了 12月27日发
每日聚焦:最快闪充旗舰!真我GT Neo5充电头曝光:支持240W充电
环球热讯:紫米裁员80%并入小米?官方澄清:ZMI品牌将继续存在
全球新资讯:9.99万元遭疯抢 五菱宏光MINI EV敞篷版下线:能跑280km
苯胺皮是什么皮?苯胺皮和纳帕皮有什么区别?
世界新动态:CloudCanal实战-五分钟搞定Oracle到StarRocks数据迁移与同步
(一)elasticsearch 编译和启动
【速看料】马斯克辞任CEO,产品经理如何用项目协作软件武装自己?
焦点速讯:字节鏖战美团的关键一役
重点聚焦!糗事百科宣布将关闭服务 自侃“享年17岁”
全球观点:神似苹果AirPower!特斯拉推出无线充电板:最高功率15W
手慢无 民族品牌两面针牙膏大促:四支到手20元还送牙刷