一道多线程面试题引起的自我救赎

反射冰川
• 阅读 6148

一道多线程面试题引起的自我救赎

近日去一个知名互联网企业参加面试,之前准备多多信心满满,但是面试一开始就是一道不起眼的编程题

数组A内容为 1,2,3,4...52 ,数组B内容为26个英文字母,使用两个线程分别输入两个数组,
打印内容为:12a34b56c78e....... 这样的规律

当时看了一下觉得so easy, 第一思路就是使用wait()/notify() 通过判断已打印的数量让两个线程交替等待。
但是装逼情绪一下来了,突然想起了没怎么使用的CyclicBarrier ,直观认为这种线程闩也能等待,还能计数
估计也能解决这个问题,于是开始设计算法,思考了很久,在纸上也推演逻辑,可怎么也想不出来,突然有种
直觉我肯定没理解透CyclicBarrier的原理,当时时间已经很紧张了,这道题就这样被我的装逼情绪给毁了,
情绪已经受到了影响,之后的面试可想而知。

CyclicBarrier 字面意思回环栅栏,通过它可以实现让一组线程等待至某个状态之后再全部同时执行。叫做回环是因为当所有等待线程都被释放以后,CyclicBarrier可以被重用。我们暂且把这个状态就叫做barrier,当调用await()方法之后,线程就处于barrier了。
就像赛马场上所有骑手都准备就位后才开始起跑一样,把这类用于解决上面的面试题完全不合适。:<


回到家里越想越气,明明几道题可以回答好却因为第一道题影响情绪,进入了防御思维方式,不能很好的发挥自己。为了惩罚,我要自己用三种解法解决上面那道面试题。

好吧,进入解决的正题。
重温一个面试题内容:

数组A内容为 1,2,3,4...52 ,数组B内容为26个英文字母,使用两个线程分别输入两个数组,
打印内容为:12a34b56c78e....... 这样的规律

  1. 提取一下核心内容,去除次要内容
    两个线程需要交替执行,打印数字的线程需要先执行,数组打印完毕后线程需要结束。

  2. 转换成模型,可以理解为 数字线程先执行,字母线程先等待,每次打印相当于一个子任务,任务完毕后
    通知另一个线程工作,自己进入等待状态,如此交替往复直到子任务全部完毕,再次通知彼此以防对方卡住。

  3. 转换成Java中的组件,可以让线程停下/启动的方式有如下几种: suspend/resume(已废弃),wait/notify(需要锁对象有点浪费) 或者 Lock/Condition, LockSupport(非常好直接等待和恢复),自旋锁(对于这个场景也不错)


下面是具体实现

  • 自旋锁

Java代码

package interview;

import java.util.concurrent.atomic.AtomicBoolean;

public class PrintNumAndChar1 {

    public static void main(String[] args) {
        AtomicBoolean isNum = new AtomicBoolean(true);
        int[] nums = { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 };
        char[] chars = { 'a', 'b', 'c', 'd', 'e' };
        new PrintNums(nums, isNum).start();
        new PrintChars(chars, isNum).start();

    }

    public static class PrintNums extends Thread {
        private int[] nums;
        private AtomicBoolean isNum;

        public PrintNums(int[] a1, AtomicBoolean isNum) {
            this.nums = a1;
            this.isNum = isNum;
        }

        public void run() {
            int count = 0;
            for (int i = 0; i < nums.length; i++) {
                while (!isNum.get()) {
                    Thread.yield();
                }
                System.out.print(nums[i]);
                count++;
                if (count == 2) {
                    isNum.set(false);
                    count = 0;
                }
            }
            isNum.set(false);
        }
    }

    public static class PrintChars extends Thread {
        private char[] chars;
        private AtomicBoolean isNum;

        public PrintChars(char[] a2, AtomicBoolean isNum) {
            this.chars = a2;
            this.isNum = isNum;
        }

        public void run() {
            int count = 0;
            for (int i = 0; i < chars.length; i++) {
                while (isNum.get()) {
                    Thread.yield();
                }
                System.out.print(chars[i]);
                count++;
                if (count == 1) {
                    isNum.set(true);
                    count = 0;
                }
            }
            isNum.set(true);
        }
    }
}

`

  • LockSupport(直接等待和恢复)

Java代码

package interview;

import java.util.concurrent.locks.LockSupport;

public class PrintNumAndChar2 {

    public static void main(String[] args) {
        int[] nums = { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 };
        char[] chars = { 'a', 'b', 'c', 'd', 'e' };
        PrintNums t1 = new PrintNums(nums);
        PrintChars t2 = new PrintChars(chars);
        t1.setPrintChars(t2);
        t2.setPrintNums(t1);
        t1.start();
        t2.start();

    }

    public static class PrintNums extends Thread {
        private int[] nums;
        private PrintChars printChars;

        public PrintNums(int[] a1) {
            super();
            this.nums = a1;
        }

        public void setPrintChars(PrintChars printChars) {
            this.printChars = printChars;
        }

        public void run() {
            int count = 0;
            for (int i = 0; i < nums.length; i++) {
                if(count==2){
                    count = 0;
                    LockSupport.unpark(printChars);
                    LockSupport.park();
                }
                System.out.print(nums[i]);
                count++;
            }
            LockSupport.unpark(printChars);
        }
    }

    public static class PrintChars extends Thread {
        private char[] chars;
        private PrintNums printNums;

        public PrintChars(char[] chars) {
            super();
            this.chars = chars;
        }

        public void setPrintNums(PrintNums printNums) {
            this.printNums = printNums;
        }

        public void run() {
            LockSupport.park();
            int count = 0;
            for (int i = 0; i < chars.length; i++) {
                if(count==1){
                    count = 0;
                    LockSupport.unpark(printNums);
                    LockSupport.park();
                }
                System.out.print(chars[i]);
                count++;
            }
            LockSupport.unpark(printNums);
        }
    }
}
  • wait/notify(需要锁对象有点浪费) 或者 Lock/Condition ,我认为最渣的实现

Java代码

package interview;

import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;

public class PrintNumAndChar3 {

    public static void main(String[] args) {
        int[] nums = { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 };
        char[] chars = { 'a', 'b', 'c', 'd', 'e' };
        Lock canPrint = new ReentrantLock();
        Condition printNum = canPrint.newCondition();
        Condition printChar = canPrint.newCondition();
        new PrintNums(nums, canPrint, printNum, printChar).start();
        new PrintChars(chars, canPrint, printNum, printChar).start();
    }

    public static class PrintNums extends Thread {
        private int[] nums;
        private Condition printNum;
        private Condition printChar;
        private Lock canPrint;

        public PrintNums(int[] nums, Lock canPrint, Condition printNum, Condition printChar) {
            super();
            this.nums = nums;
            this.printNum = printNum;
            this.printChar = printChar;
            this.canPrint = canPrint;
        }

        public void run() {
            int count = 0;
            try {
                for (int n : nums) {
                    if (count == 2) {
                        canPrint.lock();
                        count = 0;
                        printChar.signal();
                        printNum.await();
                        canPrint.unlock();
                    }
                    System.out.print(n);
                    count++;
                }
                canPrint.lock();
                printChar.signal();
                canPrint.unlock();
            } catch (InterruptedException e) {
                // TODO Auto-generated catch block
                e.printStackTrace();
            }
        }
    }

    public static class PrintChars extends Thread {
        private char[] chars;
        private Condition printNum;
        private Condition printChar;
        private Lock canPrint;

        public PrintChars(char[] chars, Lock canPrint, Condition printNum, Condition printChar) {
            super();
            this.chars = chars;
            this.printNum = printNum;
            this.printChar = printChar;
            this.canPrint = canPrint;
        }

        public void run() {
            int count = 0;
            try {
                Thread.sleep(100);
                for (char n : chars) {
                    if (count == 1) {
                        canPrint.lock();
                        count = 0;
                        printNum.signal();
                        printChar.await();
                        canPrint.unlock();
                    }
                    System.out.print(n);
                    count++;
                }
                canPrint.lock();
                printNum.signal();
                canPrint.unlock();
            } catch (InterruptedException e) {
                // TODO Auto-generated catch block
                e.printStackTrace();
            }
        }
    }
}

使用Lock锁的方式有个问题,我使用了sleep 让打印字符的线程等待了100毫秒,我没有找到合适的方式控制两个同时运行的线程的顺序,如果你有什么好方法希望也能分享出来。
记得有个朋友告诉我,要想不断提高自己就去面试吧,即使你不想换工作,在面试中确实能发现自己的不足和薄弱的地方。

点赞
收藏
评论区
推荐文章
peter peter
4年前
用 Go 如何实现精准统计文章字数
今天要聊的内容应该可以当做一道面试题,你可以先想想该怎么实现。统计字数是一个很常见的需求,很多人印象最深的应该是微博早些时候限制140字,而且边输入会边统计剩余字数。现在很多社区文章也会有字数统计的功能,而且可以依据字数来预估阅读时间。比如Go语言中文网就有这样的功能。01需求分析下手之前先分析下这个需求。从我个人经验看,
胡哥有话说 胡哥有话说
4年前
面试官在“逗”你系列:数组去重你会几种呀?
前言数组去重是一个老生常谈的话题,也是前端童鞋在面试时的一道高频题。本文将深入的探索数组去重的原理及实现,为各位小伙伴提供多种可以反手“调戏”面试官的解决方案。话不多说,上去就来一梭子...数组去重核心原理价值100W的核心原理上来就给你了...,记得留言点赞鸭!1.一般我们都会创建临时变量tmp,存储不重复的元素(以数组元素存储或对
希望的天 希望的天
4年前
经典JAVA面试题整理,方便统一复习
以下是网上整理的非常全面的面试题,当然,绝大多数人不可能全部用到,但是都罗列在此,大家可根据自己的情况,选择对应的模块进行阅读。面试题模块介绍这份面试题,包含的内容了十九个模块:Java基础、容器、多线程、反射、对象拷贝、JavaWeb模块、异常、网络、设计模式、Spring/SpringMVC、SpringBoot/SpringCloud、Hi
浩浩 浩浩
4年前
Android高频面试题:该怎样在Android面试中聊聊多线程不被忽悠?
多线程可以说是Android面试的高频问题了,而多线程涉及的内容非常多,因此在面试当中往往不知道从何说起,本文并不是为了科普多线程或者研究多线程的知识,而是尝试组织语言
Wesley13 Wesley13
3年前
2021最新拼多多面经总结!
一直忍着准备放个大招,没想到还是被刷了...前阵子,在准备拼多多的面试,好不容易挺近了三面,没想到被一波完虐,最终面试官请我回家等候通知了。在等候面试过程中,跟周围的程序员同僚聊了起来,顺便加了一手联系方式。嘿嘿,虽然没有过面试,但是东拼西凑,加上自己记下的面试题,把面试内容基本都统计了下来。!拼多多电商部java岗三面落选,记下的面试题,
Wesley13 Wesley13
3年前
Java多线程之线程池7大参数、底层工作原理、拒绝策略详解
Java多线程之线程池7大参数详解目录企业面试题线程池7大参数源码线程池7大参数详解底层工作原理详解线程池的4种拒绝策略理论简介面试的坑:线程池实际中使用哪一个?1\.企业面试题线程池的工作原理,几个重要参数,然后给了具体几个参数分析线程池会怎么做,最后问阻塞队列用是什么?线程池的构造类的方
Wesley13 Wesley13
3年前
2020年1
前言2020年一半儿快要过去了,总结了上半年各类Java面试题,初中级和中高级都有,包括JavaOOP面试题、Java集合/泛型面试题、Java异常面试题、Java种的IO与NIO面试题、Java反射面试题、Java序列化面试题、Java注解面试题、多线程与并发面试题、JVM面试题、MySQL面试题、Redis面试题、Memcached面试题、Mo
Stella981 Stella981
3年前
Golang之如何(优雅的)比较两个未知结构的json
这是之前遇到的一道面试题,后来也确实在工作中实际遇到了。于是记录一下,如何(优雅的)比较两个未知结构的json。假设,现在有两个简单的json文件。{"id":1,"name":"testjson01","isadmin":true}{"isadmi
Stella981 Stella981
3年前
Git神作!2021年Java春招高级面试指南,吃透至少P7
马上到今年的金三银四了,又是跳槽的好季节,准备跳槽的同学都摩拳擦掌准备大面好几场,今天为大家准备了互联网面试必备的1到5年Java面试者都需要掌握的面试题,分别JVM,并发编程,MySQL,Tomcat,网络与IO及Spring系列等等,可以说掌握这些薪资涨10K还是可以的!今天分享给大家的都是目前主流企业使用最高频的面试题库,也都是Java
Stella981 Stella981
3年前
Return出现在try、catch、finally中的不同执行结果
前几天,去一家公司面试,碰到一道面试题大致内容为:如果在try中存在return语句,那么finally中的语句是否会执行,如果会执行,那先后顺序又是怎样。当时自己的解题思路是:坚信大学时候,java编程基础的一句话,finally中的语句一定会执行,所以我觉得finally中的语句一定会执行,而return语句会跳出当前方法,所以return语句应该在
Stella981 Stella981
3年前
Python中实现二分查找的2种方法?
!(https://oscimg.oschina.net/oscnet/48f7b52f44a94c3f9cb8cf7bc1219260.gif)公众号新增加了一个栏目,就是每天给大家解答一道Python常见的面试题,反正每天不贪多,一天一题,正好合适,只希望这个面试栏目,给那些正在准备面试的同学,提供一点点帮助!小猿会从最基础的