前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >如何优雅的让3个线程打印ABC

如何优雅的让3个线程打印ABC

作者头像
luoxn28
发布2021-03-18 12:38:15
2.6K0
发布2021-03-18 12:38:15
举报
文章被收录于专栏:TopCoderTopCoder

让3个线程依次打印ABC,是一个常见的问题,这是阿里和华为都有考过的面试题。比如题目描述如下:

编写一个程序,开启三个线程,这三个线程按照顺序依次打印ABC,每个字母打印10次后结束,最后结果如 ABCABCABC… 依次递推

这是一道经典的多线程编程面试题,首先吐槽一下,这道题的需求很是奇葩,先开启多线程,然后再串行打印 ABC,这不是吃饱了撑的吗?不过既然是道面试题,就不管这些了,其目的在于考察多线程编程基础。

这个题目肯定是要启动3个线程的,那怎么让这3个线程“协作”按顺序打印A、B、C呢?从大的方面来讲,这种“协作”可分为以下两种:

  • 竞争型:每个线程都抢着去打印,如果发现不该自己打印,则准备下一轮抢。由于大家都是竞争的,因此需要用锁机制来保护。
  • 协同型:当前线程线程打印之后通知下一个线程去打印,这种需要确认好第一个线程打印时机。由于是协同型的因此可以不用锁机制来保护,但是需要一个通知机制。

竞争型打印

多个线程竞争型打印,优势是代码简单易懂,劣势是线程争抢是CPU调度进行的,可能该某个线程打印时结果该线程迟迟未被CPU调度,结果其他线程被CPU调度到但是由于不能执行打印操作而继续争抢,造成CPU性能浪费。示例代码如下:

代码语言:javascript
复制
@AllArgsConstructor
public class DemoTask implements Runnable {

    // 这里将lock对象换成 Lock(ReentrantLock) 进行lock/unlock也是可以的
    private static final Object lock = new Object();
    private static final int MAX = 30;
    private static int current = 0;

    private int index;

    @Override
    public void run() {
        while (current < MAX) {
            synchronized (lock) {
                if ((current < MAX) && (current % 3 == index)) {
                    System.out.println((char) ('A' + current % 3));
                    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);
    }
}

协同型打印

多个线程协同型打印,优势是各个线程使用“通知”机制进行协同分工,理论上执行效率较高,不过要使用对应的“通知”机制。关于如何“通知”,第一种是可使用Java对象的 wait/notify 或者Conditon对象的await/signal,第二种是以事件或者提交任务的方式(比如通过提交“待打印数字”这个任务给下一个线程)。

第一种方式网上对应的示例代码很多,就不在赘述。下面以第二种方式进行代码分析,打印完成之后,将待打印的数据塞给下一个线程,这样下一个线程就可以打印了,代码如下:

代码语言:javascript
复制
public static void main(String[] args) {
    ThreadTask t1 = new ThreadTask();
    ThreadTask t2 = new ThreadTask();
    ThreadTask t3 = new ThreadTask();

    t1.next = t2;
    t2.next = t3;
    t3.next = t1;

    t1.start();
    t2.start();
    t3.start();

    t1.queue.add(0);
}

public static class ThreadTask extends Thread {
    private static final Integer END = 30;

    @Getter
    private BlockingQueue<Integer> queue = new LinkedBlockingQueue<>();
    private ThreadTask next;

    @SneakyThrows
    @Override
    public void run() {
        while (true) {
            Integer take = queue.take();
            if (take >= END) {
                break;
            }

            System.out.println((char) ('A' + take % 3));
            next.queue.put(take + 1);
        }

        if (next.isAlive()) {
            next.queue.put(END);
        }
    }
}

代码中使用BlockingQueue队列,避免了 wait/notify 或者 await/signal,也能达到通知机制。注意,Java的阻塞队列是一个支持阻塞插入和移除方法的队列,阻塞队列常用于生产者和消费者场景,生产者是向队列中添加元素的线程,消费者是从队列中获取数据的线程。

阻塞队列主要包括两部分内容:一个是存放数据的容器,另一个就是线程的管理(阻塞/唤醒),前者可以基于Array或者LinkedList数据结构,后者借助于Lock/Condition来实现,也就是使用通知模式来实现的,具体可查看Lock/Condition资料。

本文参与 腾讯云自媒体同步曝光计划,分享自微信公众号。
原始发表:2021-03-16,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 TopCoder 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 竞争型打印
  • 协同型打印
相关产品与服务
容器服务
腾讯云容器服务(Tencent Kubernetes Engine, TKE)基于原生 kubernetes 提供以容器为核心的、高度可扩展的高性能容器管理服务,覆盖 Serverless、边缘计算、分布式云等多种业务部署场景,业内首创单个集群兼容多种计算节点的容器资源管理模式。同时产品作为云原生 Finops 领先布道者,主导开源项目Crane,全面助力客户实现资源优化、成本控制。
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档