一起聊聊3个线程依次打印1、2、3...的故事
3个线程依次打印1、2、3…这个问题,常常被作为面试题,题目如下:
三个线程,一个线程负责打印1,4,7,……;第二个负责打印2,5,8,……,第三个负责打印3,6,9,……,要求在控制台中按顺序输出1,2,3,4,5,6……。
这个题目肯定是要启动3个线程的,那怎么让这3个线程“协作”按顺序打印1、2、3呢?从大的方面来讲,这种“协作”可分为以下两种:
- 竞争型:每个线程都抢着去打印,如果发现不该自己打印,则准备下一轮抢。由于大家都是竞争的,因此需要用锁机制来保护。
- 协同型:当前线程线程打印之后通知下一个线程去打印,这种需要确认好第一个线程打印时机。由于是协同型的因此可以不用锁机制来保护,但是需要一个通知机制。
竞争型打印
多个线程竞争型打印,优势是代码简单易懂,劣势是线程争抢是CPU调度进行的,可能该某个线程打印时结果该线程迟迟未被CPU调度,结果其他线程被CPU调度到但是由于不能执行打印操作而继续争抢,造成CPU性能浪费。示例代码如下:
public class DemoTask implements Runnable {
// 这里将lock对象换成 Lock(ReentrantLock) 进行lock/unlock也是可以的
private static final Object lock = new Object();
private static final int MAX = 1024;
private static int current = 1;
private int index;
public DemoTask(int index) {
this.index = index;
}
@Override
public void run() {
while (current <= MAX) {
synchronized (lock) {
if ((current <= MAX) && (current % 3 == index)) {
System.out.println(current++);
}
}
}
}
public static void main(String[] args) throws Exception {
List<Thread> threadList = Arrays.asList(
new Thread(new DemoTask(0)),
new Thread(new DemoTask(1)),
new Thread(new DemoTask(2))
);
threadList.forEach(Thread::start);
for (Thread thread : threadList) {
thread.join();
}
}
}
协同型打印
多个线程协同型打印,优势是各个线程使用“通知”机制进行协同分工,理论上执行效率较高,不过要使用对应的“通知”机制。关于如何“通知”,第一种是可使用Java对象的 wait/notify
或者Conditon对象的 await/signal
,第二种是以事件或者提交任务的方式(比如通过提交“待打印数字”这个任务给下一个线程)。
下面以第二种方式进行代码分析,比如当前线程通过submit给下一个线程一个“待打印数字”的任务,这样很容易想到使用只包含1个线程的线程池来实现,示例代码如下:
public class DemoTask implements Runnable {
// 主线程要等待线程打印完毕,使用CountDownLatch通知机制
private static CountDownLatch countDownLatch = new CountDownLatch(1);
private static List<ExecutorService> threadList = new ArrayList<>();
private static final int MAX = 1024;
private static int current = 1;
@Override
public void run() {
if (current <= MAX) {
System.out.println(current++);
threadList.get(current % threadList.size()).submit(new DemoTask());
} else {
countDownLatch.countDown();
}
}
public static void main(String[] args) throws InterruptedException {
for (int i = 0; i < 3; i++) {
threadList.add(Executors.newFixedThreadPool(1));
}
threadList.get(0).submit(new DemoTask());
countDownLatch.await();
threadList.forEach(ExecutorService::shutdown);
}
}
推荐阅读
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· AI与.NET技术实操系列:基于图像分类模型对图像进行分类
· go语言实现终端里的倒计时
· 如何编写易于单元测试的代码
· 10年+ .NET Coder 心语,封装的思维:从隐藏、稳定开始理解其本质意义
· .NET Core 中如何实现缓存的预热?
· 25岁的心里话
· 闲置电脑爆改个人服务器(超详细) #公网映射 #Vmware虚拟网络编辑器
· 基于 Docker 搭建 FRP 内网穿透开源项目(很简单哒)
· 零经验选手,Compose 一天开发一款小游戏!
· 通过 API 将Deepseek响应流式内容输出到前端