经典面试题-两个线程交替打印奇数和偶数

点击上方蓝色“趣学程序”,选择“设为星标”

回复“资源”获取独家整理的学习资料!

回复“加群”与更多小伙伴共同成长!

回复“源码”获取专属项目源码!

前提

今天下班时候和同事聊天偶然听到面试题“两个线程交替打印奇数和偶数”的实现,这里做一个复盘。

复盘

场景一:线程A打印奇数,线程B打印偶数,线程A和线程B交替打印,使用对象监视器实现。

场景二:线程A打印奇数,线程B打印偶数,线程A和线程B交替打印,使用JDK提供的并发类库实现。

这两个场景中,场景一是一种比较古老的同步方式,本质由JVM实现;场景二是JDK1.5引入JUC包之后简化了并发编程的前提下的更简便的实现。下面针对两个场景做对应的实现。

场景一

场景一中,线程A和线程B交替打印奇数和偶数,使用对象监视器实现,通俗来说:线程A或线程B只要有一者竞争锁成功,就打印++i,通知其他线程从等待集合中释放,然后自身线程加入等待集合并且释放锁即可。

public class OddEvenPrinter {

    private final Object monitor = new Object();
    private final int limit;
    private volatile int count;

    public OddEvenPrinter(int limit, int initCount) {
        this.limit = limit;
        this.count = initCount;
    }

    public void print() {
        synchronized (monitor) {
            while (count < limit) {
                try {
                    System.out.println(String.format("线程[%s]打印数字:%d", Thread.currentThread().getName(), ++count));
                    monitor.notifyAll();
                    monitor.wait();
                } catch (InterruptedException e) {
                    //ignore
                }
            }
        }
    }

    public static void main(String[] args) throws Exception {
        OddEvenPrinter printer = new OddEvenPrinter(10, 0);
        Thread thread1 = new Thread(printer::print, "thread-1");
        Thread thread2 = new Thread(printer::print, "thread-2");
        thread1.start();
        thread2.start();
        Thread.sleep(Integer.MAX_VALUE);
    }
}

执行后的输出结果:

线程[thread-1]打印数字:1
线程[thread-2]打印数字:2
线程[thread-1]打印数字:3
线程[thread-2]打印数字:4
线程[thread-1]打印数字:5
线程[thread-2]打印数字:6
线程[thread-1]打印数字:7
线程[thread-2]打印数字:8
线程[thread-1]打印数字:9
线程[thread-2]打印数字:10

场景二

场景二中,如果需要使用JUC中提供的并发类库,可以考虑和对象监视器功能接近的可重入锁ReentrantLock。具体代码如下:

public class OddEvenPrinterEx {

    private final ReentrantLock lock = new ReentrantLock();
    private final Condition condition = lock.newCondition();

    private final int limit;
    private volatile int count;

    public OddEvenPrinterEx(int limit, int initCount) {
        this.limit = limit;
        this.count = initCount;
    }

    public void print()  {
        lock.lock();
        try {
           while (count < limit){
               System.out.println(String.format("线程[%s]打印数字:%d", Thread.currentThread().getName(), ++count));
               condition.signalAll();
               try {
                   condition.await();
               } catch (InterruptedException e) {
                   //ignore
               }
           }
        } finally {
            lock.unlock();
        }
    }

    public static void main(String[] args) throws Exception {
        OddEvenPrinterEx printer = new OddEvenPrinterEx(10, 0);
        Thread thread1 = new Thread(printer::print, "thread-1");
        Thread thread2 = new Thread(printer::print, "thread-2");
        thread1.start();
        thread2.start();
        Thread.sleep(Integer.MAX_VALUE);
    }
}

执行后的输出结果:

线程[thread-2]打印数字:1
线程[thread-1]打印数字:2
线程[thread-2]打印数字:3
线程[thread-1]打印数字:4
线程[thread-2]打印数字:5
线程[thread-1]打印数字:6
线程[thread-2]打印数字:7
线程[thread-1]打印数字:8
线程[thread-2]打印数字:9
线程[thread-1]打印数字:10

眼尖的可能看到这里是先由thread-2打印奇数,然后thread-1打印偶数,这个和同步器框架的等待队列以及同步队列的竞争有关。

小结

这个问题有很多种解决思路,但是目前笔者没想到无锁实现方案。很多现成的(参考多个博客)方案里面都是使用各种多重同步或者加锁,其实意义是不大,实际上要理解对象监视器和同步器框架AQS的一些原理,那么实现起来自然比较简单。参看笔者之前写的两篇文章:

  • 深入理解Object提供的阻塞和唤醒API

  • JUC同步器框架AbstractQueuedSynchronizer源码图文分析

往期推荐

Mybatis面试18问,你想知道的都在这里了!牛,微信支付架构竟然是这么实现的请你详细说说类加载流程,类加载机制及自定义类加载器说一下HashMap的实现原理?Lombok天天用,却不知道它的原理是什么?

扫描二维码

获取更多精彩

趣学程序

本课程隶属于自然语言处理(NLP)实战系列。自然语言处理(NLP)是数据科学里的一个分支,它的主要覆盖的内容是:以一种智能与高效的方式,对文本数据进行系统化分析、理解与信息提取的过程。通过使用NLP以及它的组件,我们可以管理非常大块的文本数据,或者执行大量的自动化任务,并且解决各式各样的问题,如自动摘要,机器翻译,命名实体识别,关系提取,情感分析,语音识别,以及主题分割等等。 一般情况下一个初级NLP工程师的工资从15万-35万不等,所以掌握NLP技术,对于人工智能学习者来讲是非常关键的一个环节。 【超实用课程内容】 课程从自然语言处理的基本概念与基本任务出发,对目前主流的自然语言处理应用进行全面细致的讲解,包括文本分类,文本摘要提取,文本相似度,文本情感分析,文本特征提取等,同时算法方面包括经典算法与深度学习算法的结合,例如LSTM,BiLSTM等,并结合京东电商评论分类、豆瓣电影摘要提取、今日头条舆情挖掘、饿了么情感分析等过个案例,帮助大家熟悉自然语言处理工程师在工作中会接触到的常见应用的实施的基本实施流程,从0-1入门变成自然语言处理研发工程师。 【课程如何观看?】 PC端:https://edu.csdn.net/course/detail/25649 移动端:CSDN 学院APP(注意不是CSDN APP哦) 本课程为录播课,课程2年有效观看时长,大家可以抓紧时间学习后一起讨论哦~ 【学员专享增值服务】 源码开放 课件、课程案例代码完全开放给你,你可以根据所学知识,自行修改、优化 下载方式:电脑登录https://edu.csdn.net/course/detail/25649,点击右下方课程资料、代码、课件等打包下载 通过第二课时下载材料
【为什么购买本课程?】 1、学会Photoshop脚本,可以大幅提高您的工作效率、摆脱大量重复设计任务的束缚; 2、扩展Photoshop的功能:天气预报、to-do设计任务管理、中文加拼音、图层文字中英互译、每日一句英语、OCR智能识别图片上的文字内容、 为上万影片批量生成九宫格预览图、为数百个视频自动添加内容不同的片头、自动获取图片的主题颜色、快速生成不限数量并且不重复的漂亮卡通头像、给Photoshop添加猜数字、贪吃蛇游戏等等; 3、利用人工智能技术:在Photoshop中识别图片中指定颜色的物体、进行面部识别; 4、掌握99%Photoshop设计师不曾接触的技能,打造自己的职场护城河! 5、重要的是:只有我们这里提供系统、全面、易学的Photoshop脚本教程,只此一家,别无选择! 【Photoshop脚本是什么?】 * Photoshop神秘和强大的一项秘技! * 它可以允许您以代码的方式来操作文档、美化图像、处理图层、控制通道、编辑选区、使用滤镜等等,就像在Photoshop界面上操作一样。 * 与PhotoShop动作(Action)相比,PhotoShop脚本更强大、更智能、更富有逻辑判断功能。 * PhotoShop脚本主要用于重复性的任务或用于制作非常复杂的特殊效果。 【学会Photoshop脚本,可以做什么?】 * 一键给n个图片批量添加水印; * 一键将n个图片批量生成指定尺寸的缩略图; * 一键将PSD网页设计稿的各功能区域,批量输出为Web所用格式; * 一键生成iOS、Andriod应用和游戏必需的十几种尺寸的图标; * 一键将n个小图拼合为一张大图,并输出各小图在大图中的坐标信息; * 甚至开发一款运行在Photoshop上的趣味游戏! 我们向您保证,学习PhotoShop脚本所花费的时间,可以在完成几项重复性的复杂任务时得到补偿。快来学习Photoshop脚本吧! 【课程的特点】 1、创新的教学模式:手把手教您Photoshop自动化技术,一看就懂,一学就会; 2、贴心的操作提示:让您的眼睛始终处于操作的焦点位置,不用再满屏找光标; 3、语言简洁精练:瞄准问题的核心所在,减少对思维的干扰,并节省您宝贵的时间; 4、视频短小精悍:即方便于您的学习和记忆,也方便日后对功能的检索;
©️2020 CSDN 皮肤主题: 撸撸猫 设计师:设计师小姐姐 返回首页