阿里java多线程面试题:两个线程打印奇数和偶数,N个线程循环打印
直接进入正题:
通过N个线程顺序循环打印从0至100,如给定N=3则输出:
thread0: 0
thread1: 1
thread2: 2
thread0: 3
thread1: 4
.....
复制代码
一些经常回答面试题的朋友之前肯定遇到过以下问题:
两个线程交替打印0~100的奇偶数:
偶线程:0
奇线程:1
偶线程:2
奇线程:3
复制代码
这两个问题看起来是一样的。第二个问题稍微简单一些。可以先想一下如何在两个线程之间打印奇数和偶数。
两个线程打印奇数和偶数
这里可能有人使用了一个技巧,使用一个线程for循环,在每个循环中它会评估它是奇数还是偶数,然后打印出我们想要的结果。 。这种违背问题初衷的做法我们这里不予讨论。
其实,做这道题,我们需要控制两个线程的执行顺序。执行完偶数线程后,执行奇数线程。这有点像警告机制。偶数线程向奇数线程发出警报,然后奇数线程向偶数线程发出警报。一看到notify/wait,有的朋友立刻想到了Object中的wait和notify。没错,这里我们使用wait和notify来实现。代码如下:
public class 交替打印奇偶数 {
static class SoulutionTask implements Runnable{
static int value = 0;
@Override
public void run() {
while (value <= 100){
synchronized (SoulutionTask.class){
System.out.println(Thread.currentThread().getName() + ":" + value++);
SoulutionTask.class.notify();
try {
SoulutionTask.class.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
}
public static void main(String[] args) {
new Thread(new SoulutionTask(), "偶数").start();
new Thread(new SoulutionTask(), "奇数").start();
}
}
复制代码
这里我们有两个线程,用来通过notify和wait来控制我们线程的执行,来打印我们目标的结果
N个线程循环打印
让回到我们最初的问题,N个线程循环打印。我帮群里的人回答完这个问题后,我又把它扔回群里。很多经验丰富的司机以前都见过奇数偶数交替打印的问题,所以我立即做了几个版本。我们看一下老司机1的代码:
public class 老司机1 implements Runnable {
private static final Object LOCK = new Object();
/**
* 当前即将打印的数字
*/
private static int current = 0;
/**
* 当前线程编号,从0开始
*/
private int threadNo;
/**
* 线程数量
*/
private int threadCount;
/**
* 打印的最大数值
*/
private int maxInt;
public 老司机1(int threadNo, int threadCount, int maxInt) {
this.threadNo = threadNo;
this.threadCount = threadCount;
this.maxInt = maxInt;
}
@Override
public void run() {
while (true) {
synchronized (LOCK) {
// 判断是否轮到当前线程执行
while (current % threadCount != threadNo) {
if (current > maxInt) {
break;
}
try {
// 如果不是,则当前线程进入wait
LOCK.wait();
} catch (Exception e) {
e.printStackTrace();
}
}
// 最大值跳出循环
if (current > maxInt) {
break;
}
System.out.println("thread" + threadNo + " : " + current);
current++;
// 唤醒其他wait线程
LOCK.notifyAll();
}
}
}
public static void main(String[] args) {
int threadCount = 3;
int max = 100;
for (int i = 0; i < threadCount; i++) {
new Thread(new 老司机1(i, threadCount, max)).start();
}
}
}
复制代码
核心方法正在运行。可以看到奇偶数交替打印的原理和我们类似。这里我们将通知更改为notifyAll。这里需要注意的是,很多人都会将notifyAll理解为意味着所有其他等待线程都将被执行。这其实是错误的。这里,只有等待的线程才会从当前的等待状态中释放出来,也称为唤醒。由于我们在这里用同步块包装了它,因此被唤醒的线程将撕裂同步锁。
这个老司机代码其实是可以通过的,但是有什么问题呢?当我们的线程数很大时,因为我们不确定唤醒的线程是否是下一个要运行的线程,所以它可能会获取锁,但不应该自行运行它,然后进入等待状态。比如现在有100个线程。线程,现在第一个线程正在运行。它执行完之后需要另外一个线程来运行,但是第100个线程抢到了,发现不是自己,然后进入wait,然后第99个线程抢到了,发现不是自己,然后进入wait再次,然后是第98个、第97个……直到第三个线程抢到了,最后第二个线程抢到了同步锁。在这里,即使最终能够完成目标,很多过程也会白费力气。
还有其他经验丰富的驱动程序也使用锁/状态实现了此功能,并且还有一些经验丰富的驱动程序使用了相对较新的方法(例如队列)来实现此功能。当然,这里我就不提了。一般原则是基于上述。这里我想谈谈我的做法。 Java多线程中提供了一些常用的同步设备。这种场景下,使用Semaphore比较合适,也就是信号量。我们的前一个线程保存下一个线程的信号量。所有这些都通过信号量数组相关。代码如下:
static int result = 0;
public static void main(String[] args) throws InterruptedException {
int N = 3;
Thread[] threads = new Thread[N];
final Semaphore[] syncObjects = new Semaphore[N];
for (int i = 0; i < N; i++) {
syncObjects[i] = new Semaphore(1);
if (i != N-1){
syncObjects[i].acquire();
}
}
for (int i = 0; i < N; i++) {
final Semaphore lastSemphore = i == 0 ? syncObjects[N - 1] : syncObjects[i - 1];
final Semaphore curSemphore = syncObjects[i];
final int index = i;
threads[i] = new Thread(new Runnable() {
public void run() {
try {
while (true) {
lastSemphore.acquire();
System.out.println("thread" + index + ": " + result++);
if (result > 100){
System.exit(0);
}
curSemphore.release();
}
} catch (Exception e) {
e.printStackTrace();
}
}
});
threads[i].start();
}
}
复制代码
这样我们就不会白引发线程了。每个线程都会按照我们约定的顺序执行。其实面试官也是这么说的。在你需要测试的地方,每个线程的性能都可以掌控在你的手中。这也可以确认你的多线程知识是否扎实。
作者:咖啡拿铁
链接:https://juejin.im/post/5c89b9515188257e5b2befdd
来源:掘金
版权归作者所有。商业转载请联系作者获取授权。非商业转载请注明来源。
版权声明
本文仅代表作者观点,不代表Code前端网立场。
本文系作者Code前端网发表,如需转载,请注明页面地址。
发表评论:
◎欢迎参与讨论,请在这里发表您的看法、交流您的观点。