我正在处理以下面试问题:
一个进程中有三个线程。第一个线程打印1 1 1 ...,第二个打印2 2 2 ...,第三个打印3 3 3 ...无休止您如何安排这三个线程以便 打印1 2 3 1 2 3 ...
我想出了下面的代码,该代码使用两个线程打印1 2 1 2 1 2
,但是我无法从第三个线程中弄清楚如何打印数字3
的条件。
public class PrintOneTwoThree {
private static boolean isFirst = true;
private static final Object lock = new Object();
public static void main(String[] args) {
// first thread
new Thread(() -> {
try {
synchronized (lock) {
for (;;) {
while (!isFirst) {
lock.wait();
}
System.out.print("1 ");
isFirst = false;
lock.notify();
}
}
} catch (InterruptedException ignored) {
}
}).start();
// second thread
new Thread(() -> {
try {
synchronized (lock) {
for (;;) {
while (isFirst) {
lock.wait();
}
System.out.print("2 ");
isFirst = true;
lock.notify();
}
}
} catch (InterruptedException ignored) {
}
}).start();
}
}
如何有效解决此类问题?
答案 0 :(得分:2)
下面是一个通用的工作示例,该示例使用计数器为n
个线程一次授予一个线程的权限:
public class PrintOneTwoThree {
private static int currentTask;
private static int totalThreads;
private static final Object lock = new Object();
public static void main(String[] args) {
currentTask = 0;
totalThreads = 3;
for (int i = 0; i < totalThreads; i++) {
createThread(i);
}
}
static void createThread(int id) {
new Thread(() -> {
try {
for (;;) {
synchronized (lock) {
while (currentTask != id) {
lock.wait();
}
System.out.print(id + 1 + " ");
currentTask = (currentTask + 1) % totalThreads;
lock.notifyAll();
}
}
}
catch (InterruptedException ignored) {}
}).start();
}
}
输出:
1 2 3 1 2 3 1 2 3 ...
几句话:
notify()
可以在2线程版本上正常工作(因为该变量上最多有一个其他线程阻塞),但是如果一个线程退出关键部分,则在3+版本中将死锁notify
表示条件变量currentTask
可用,但错误的线程赢得了获得锁的竞争。我不确定notifyAll()
是否在这里是合适的设计,因为只有一个线程可以取得进展,因此似乎可以重新检查条件谓词并使用notify()
。 / p>
我将for (;;)
移到了同步部分的外面,以使线程安全范围尽可能狭窄。该示例之所以人为设计,是因为如果您想要访问单个资源的确切行为,而没有其他任何事情,那么增加线程的开销就没有意义了,您也可以确定性地在单个线程中完成。在一个真实的示例中,线程在不阻塞条件变量的情况下会在for (;;)
循环中的其他位置执行线程安全的工作,因此考虑到这一点似乎是合乎逻辑的。
答案 1 :(得分:1)
代替布尔标志,使用整数计数器,并在除以3时检查余数,并在每次打印后检查增量计数器。
而且由于有多个线程在同一锁上等待,所以最好使用notifyAll
。
private static int counter = 0;
new Thread(() -> {
try {
synchronized (lock) {
for (;;) {
while (counter % 3 != 0) {
lock.wait();
}
System.out.print("1 ");
++counter;
lock.notifyAll();
}
}
} catch (InterruptedException ignored) {
}
}).start();
//And the same stuff for other two threads just replacing value for remainder in if and value in print
答案 2 :(得分:0)
public class PrintOneTwoThree {
public static void main(String[] args) {
Printers sp = new Printers();
ExecutorService executor = Executors.newFixedThreadPool(3);
executor.submit(new FirstNumberProducer(sp, 9));
executor.submit(new SecondNumberProducer(sp , 9));
executor.submit(new ThirdNumberProducer(sp , 9));
executor.shutdown();
}
}
class Printers {
Semaphore first = new Semaphore(1);
Semaphore second = new Semaphore(0);
Semaphore third = new Semaphore(0);
public void printFirstNumber() {
try {
first.acquire();
}catch(InterruptedException exception) {
}
System.out.print("1");
second.release();
}
public void printSecondNumber() {
try {
second.acquire();
}catch(InterruptedException exception) {
}
System.out.print("2");
third.release();
}
public void printThirdNumber() {
try {
third.acquire();
}catch(InterruptedException exception) {
}
System.out.print("3");
first.release();
}
}
class FirstNumberProducer implements Runnable {
Printers sp;
int index;
FirstNumberProducer(Printers sp , int index) {
this.sp = sp;
this.index = index;
}
@Override
public void run() {
for(int i = 1 ; i <= index ; i = i + 3 ) {
sp.printFirstNumber();
}
}
}
class SecondNumberProducer implements Runnable{
Printers sp;
int index;
SecondNumberProducer(Printers sp , int index) {
this.sp = sp;
this.index = index;
}
@Override
public void run() {
for(int i = 2 ; i <= index ; i = i + 3) {
sp.printSecondNumber();
}
}
}
class ThirdNumberProducer implements Runnable{
Printers sp;
int index;
ThirdNumberProducer(Printers sp , int index) {
this.sp = sp;
this.index = index;
}
@Override
public void run() {
for(int i = 3 ; i <= index ; i = i + 3) {
sp.printThirdNumber();
}
}
}
程序输出为:
123123123