你会这道阿里多线程面试题吗?
背景
在前几天,群里有个群友问了我一道面试阿里的时候遇到的多线程题目,这个题目比较有意思,在这里和大家分享一下。
废话不多说,直接上题目:
通过N个线程顺序循环打印从0至100,如给定N=3则输出: thread0: 0 thread1: 1 thread2: 2 thread0: 3 thread1: 4 .....
一些经常刷面试题的朋友,之前肯定遇到过下面这个题目:
两个线程交替打印0~100的奇偶数: 偶线程:0 奇线程:1 偶线程:2 奇线程:3
这两个题目看起来相似,第二个题目稍微来说比较简单一点,大家可以先思考一下两个线程奇偶数如何打印。
两线程奇偶数打印
有一些人这里可能会用讨巧的,用一个线程进行循环,在每次循环里面都会做是奇数还是偶数的判断,然后打印出这个我们想要的结果。在这里我们不过多讨论这种违背题目本意的做法。
其实要做这个题目我们就需要控制两个线程的执行顺序,偶线程执行完之后奇数线程执行,这个有点像通知机制,偶线程通知奇线程,奇线程再通知偶线程。而一看到通知/等待,立马就有朋友想到了Object中的wait和notify。没错,这里我们用wait和notify对其进行实现,代码如下:
public class 交替打印奇偶数 { static class SoulutionTask implements Runnable{ static int value = 0; @Override public void run() { while (value <= 100){ synchronized (SoulutionTask.class){ System.out.println(Thread.currentThread().getName() + ":" + value++); SoulutionTask.class.notify(); try { SoulutionTask.class.wait(); } catch (InterruptedException e) { e.printStackTrace(); } } } } } public static void main(String[] args) { new Thread(new SoulutionTask(), "偶数").start(); new Thread(new SoulutionTask(), "奇数").start(); } }
这里我们有两个线程,通过notify和wait用来控制我们线程的执行,从而打印出我们目标的结果
N个线程循环打印
再回到我们最初的问题来,N个线程进行循环打印,这个问题我再帮助群友解答了之后,又再次把这个问题在群里面抛了出来,不少老司机之前看过交替打印奇偶数这道题目,于是马上做出了几个版本,让我们看看老司机1的代码:
public class 老司机1 implements Runnable { private static final Object LOCK = new Object(); /** * 当前即将打印的数字 */ private static int current = 0; /** * 当前线程编号,从0开始 */ private int threadNo; /** * 线程数量 */ private int threadCount; /** * 打印的最大数值 */ private int maxInt; public 老司机1(int threadNo, int threadCount, int maxInt) { this.threadNo = threadNo; this.threadCount = threadCount; this.maxInt = maxInt; } @Override public void run() { while (true) { synchronized (LOCK) { // 判断是否轮到当前线程执行 while (current % threadCount != threadNo) { if (current > maxInt) { break; } try { // 如果不是,则当前线程进入wait LOCK.wait(); } catch (Exception e) { e.printStackTrace(); } } // 最大值跳出循环 if (current > maxInt) { break; } System.out.println("thread" + threadNo + " : " + current); current++; // 唤醒其他wait线程 LOCK.notifyAll(); } } } public static void main(String[] args) { int threadCount = 3; int max = 100; for (int i = 0; i < threadCount; i++) { new Thread(new 老司机1(i, threadCount, max)).start(); } } }
核心方法在run里面,可以看见和我们交替打印奇偶数原理差不多,这里将我们的notify改成了notifyAll,这里要注意一下很多人会将notifyAll理解成其他wait的线程全部都会执行,其实是错误的。这里只会将wait的线程解除当前wait状态,也叫作唤醒,由于我们这里用同步锁synchronized块包裹住,那么唤醒的线程会做会抢夺同步锁。
这个老司机的代码的确能跑通,但是有一个问题是什么呢?当我们线程数很大的时候,由于我们不确定唤醒的线程到底是否是下一个要执行的就有可能会出现抢到了锁但不该自己执行,然后又进入wait的情况,比如现在有100个线程,现在是第一个线程在执行,他执行完之后需要第二个线程执行,但是第100个线程抢到了,发现不是自己然后又进入wait,然后第99个线程抢到了,发现不是自己然后又进入wait,然后第98,97...直到第3个线程都抢到了,最后才到第二个线程抢到同步锁,这里就会白白的多执行很多过程,虽然最后能完成目标。
还有其他老司机用lock/condition也实现了这样的功能,还有老司机用比较新颖的方法比如队列去做,当然这里就不多提了,大致的原理都是基于上面的,这里我说一下我的做法,在Java的多线程中提供了一些常用的同步器,在这个场景下比较适合于使用Semaphore,也就是信号量,我们上一个线程持有下一个线程的信号量,通过一个信号量数组将全部关联起来,代码如下:
static int result = 0; public static void main(String[] args) throws InterruptedException { int N = 3; Thread[] threads = new Thread[N]; final Semaphore[] syncObjects = new Semaphore[N]; for (int i = 0; i < N; i++) { syncObjects[i] = new Semaphore(1); if (i != N-1){ syncObjects[i].acquire(); } } for (int i = 0; i < N; i++) { final Semaphore lastSemphore = i == 0 ? syncObjects[N - 1] : syncObjects[i - 1]; final Semaphore curSemphore = syncObjects[i]; final int index = i; threads[i] = new Thread(new Runnable() { public void run() { try { while (true) { lastSemphore.acquire(); System.out.println("thread" + index + ": " + result++); if (result > 100){ System.exit(0); } curSemphore.release(); } } catch (Exception e) { e.printStackTrace(); } } }); threads[i].start(); } }
通过这种方式,我们就不会有白白唤醒的线程,每一个线程都按照我们所约定的顺序去执行,这其实也是面试官所需要考的地方,让每个线程的执行都能再你手中得到控制,这也可以验证你多线程知识是否牢固。
最后这篇文章被我收录于JGrowing-Java面试篇,一个全面,优秀,由社区一起共建的Java学习路线,如果您想参与开源项目的维护,可以一起共建,github地址为:https://github.com/javagrowing/JGrowing 麻烦给个小星星哟。
如果大家觉得这篇文章对你有帮助,你的关注和转发是对我最大的支持,O(∩_∩)O:

低调大师中文资讯倾力打造互联网数据资讯、行业资源、电子商务、移动互联网、网络营销平台。
持续更新报道IT业界、互联网、市场资讯、驱动更新,是最及时权威的产业资讯及硬件资讯报道平台。
转载内容版权归作者及来源网站所有,本站原创内容转载请注明来源。
- 上一篇
从零开始社区之路,手把手教你开源自己的Python包
要融入社区,第一步当然是要撰写一个自己的包。整个过程主要分为五步: 项目创建 搭建虚拟运行环境 编写项目代码 编写安装脚本 上传PyPi GIT 创建项目 创建项目,确定项目名称,description, license等: 项目地址:https://github.com/shikanon/BaiduMapAPI 搭建虚拟环境 我们在搭建自己的库的时候,是希望有一个干净的项目环境的,这时候virtualenv就很有用了,采用 virtualev 搭建虚拟环境,可以方便为后面生成私有项目的 requirement.txt 依赖包文件。 创建虚拟环境 virtualev venv 启用 virtualev : source venv/Script/activate 构建项目代码 简单,快速构建框架原型和骨架,记得包之间需要 __init__.py 文件,后面在编写setup.py也会很有用。 项目结构: 构建好架构后,可以开始编写单元测试代码,pytest是个简单易用的库,可以帮助我们快速完成单元测试构建。 构建安装脚本,编写 setup.py 文件 完成代码构建和测试就可以开始进入构建安...
- 下一篇
人脑认知科学对人工智能的启示
读《怪诞脑科学:战胜焦虑、混乱、拖延的自控术》有感 最近一段时间,一直在琢磨更好的AutoML,像我这样的懒人,当然希望能最大限度的发挥自动化的威力。 从决策树到随机森林,从支持向量机到神经网络,从遗传算法到强化学习,我发现他们都只是解决了数据转换和模式发现的问题,并未解决智能的问题。 如何让算法更智能?一有时间,我就会到互联网上去搜刮,可是截止到目前,并没有找到让我眼前一亮的新思路。 直到我读了一本书《怪诞脑科学:战胜焦虑、混乱、拖延的自控术》,颇有心得,遂记录下来,以便日后查阅 可能有人会觉得奇怪了,一本并非计算机科学领域的书,怎么能给人工智能的设计带来启示呢?我觉得好奇的朋友也可以看看,微信阅读上就能看到(涉嫌免费打广告了 ;-)) 1."克鲁机"(kluge) 这是什么鬼?作者在文中大量使用了“克鲁机”这个词语,反复的强调了一个观点:不要把人脑看的过于神秘,人脑只不过是漫长进化的产物,而进化过程中难免会有失败的作品(比如盲肠等),所以人脑也是类似的(小脑和脑干也很原始),人的身体中充斥着各种“克鲁机”。 虽然没办法找到确凿的证据,但我总感觉“克鲁机”的观点似乎好有道理。所以,我...
相关文章
文章评论
共有0条评论来说两句吧...
文章二维码
点击排行
推荐阅读
最新文章
- Red5直播服务器,属于Java语言的直播服务器
- CentOS7编译安装Cmake3.16.3,解决mysql等软件编译问题
- CentOS关闭SELinux安全模块
- CentOS7编译安装Gcc9.2.0,解决mysql等软件编译问题
- SpringBoot2整合MyBatis,连接MySql数据库做增删改查操作
- SpringBoot2整合Redis,开启缓存,提高访问速度
- SpringBoot2更换Tomcat为Jetty,小型站点的福音
- SpringBoot2编写第一个Controller,响应你的http请求并返回结果
- Eclipse初始化配置,告别卡顿、闪退、编译时间过长
- CentOS7安装Docker,走上虚拟化容器引擎之路