一道多线程面试题引起的自我救赎
近日去一个知名互联网企业参加面试,之前准备多多信心满满,但是面试一开始就是一道不起眼的编程题
数组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....... 这样的规律
提取一下核心内容,去除次要内容
两个线程需要交替执行,打印数字的线程需要先执行,数组打印完毕后线程需要结束。转换成模型,可以理解为 数字线程先执行,字母线程先等待,每次打印相当于一个子任务,任务完毕后
通知另一个线程工作,自己进入等待状态,如此交替往复直到子任务全部完毕,再次通知彼此以防对方卡住。转换成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毫秒,我没有找到合适的方式控制两个同时运行的线程的顺序,如果你有什么好方法希望也能分享出来。
记得有个朋友告诉我,要想不断提高自己就去面试吧,即使你不想换工作,在面试中确实能发现自己的不足和薄弱的地方。
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。