一個(gè)老掉牙的java面試問題 , 多線程交替打印。
有打印 ABC 的, 有打印 123 的, 有打印到100的 。
其實(shí)都一樣。
ps: 最近好多小伙伴問這個(gè),這個(gè)題這么熱門么?
實(shí)例實(shí)戰(zhàn)思路:
拿一個(gè)來做示例, 就交替打印ABC. (文末也說下從1到100的)
一起看看這個(gè)小題目 :
主角
三個(gè)線程 線程A 線程 B 線程 C
要做的事
交替打印 A B C
那就是 線程A 負(fù)責(zé)打印 A ,線程 B 負(fù)責(zé)打印 B ,線程C 負(fù)責(zé)打印 C 。
簡(jiǎn)單分析:
A線程打印完, B線程 打印 ;
B線程打印完 ,C 線程打印。
也就是說,這3個(gè)線程 ABC ,有順序/協(xié)作 。
那么這三個(gè)家伙 怎么知道自己要打印東西呢?
那必然是通知 和等待 。
思路:
三個(gè)線程都 準(zhǔn)備就緒, 準(zhǔn)備大展身手。
接到攜帶暗號(hào)的通知(默認(rèn)暗號(hào)為 A) -> 核對(duì)通知的暗號(hào) -> 打印->然后修改暗號(hào)-> 發(fā)出攜帶暗號(hào)通知(讓別的線程認(rèn)領(lǐng))
如果接到通知,發(fā)現(xiàn)暗號(hào)不對(duì),怎么辦呢? 繼續(xù)進(jìn)入等待,等待下個(gè)通知的到來。
簡(jiǎn)圖:

三個(gè)線程的一舉一動(dòng) :

ps:
如果是線程A打印完,就把通知暗號(hào)改成B,并發(fā)出通知;
如果是線程B打印完,就把通知暗號(hào)改成C,并發(fā)出通知;
同樣,如果是線程C打印完,就把通知改回A,繼續(xù)發(fā)通知。
代碼 :
寫個(gè)ReentrantLock條件控制來搞一下。
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
import java.util.concurrent.locks.Condition;
public class DoTest {
//控制三個(gè)線程 ABC,保證同一時(shí)刻只有一個(gè)線程工作
private static Lock lock = new ReentrantLock(true);
// Condition ,控制等待或是 通知
private static Condition conditionA = lock.newCondition();
private static Condition conditionB = lock.newCondition();
private static Condition conditionC = lock.newCondition();
//默認(rèn)的通知 暗號(hào) A
private static String CODE= "A";
public static void main(String[] args) {
Thread A = new Thread(() -> {
while (true) {
try {
printA();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
});
Thread B = new Thread(() -> {
while (true) {
try {
printB();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
});
Thread C = new Thread(() -> {
while (true) {
try {
printC();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
});
A.start();
B.start();
C.start();
}
public static void printA() throws InterruptedException {
//等待
lock.lock();
try {
//核對(duì)
while (!CODE.equals("A")) {
//暗號(hào)不對(duì),就進(jìn)入等待
conditionA.await();
}
System.out.println("A");
//改暗號(hào),通知B
CODE = "B";
conditionB.signalAll();
} finally {
lock.unlock();
}
}
public static void printB() throws InterruptedException {
lock.lock();
try {
while (!CODE.equals("B")) {
conditionB.await();
}
System.out.println("B");
//改暗號(hào),通知C
CODE = "C";
conditionC.signalAll();
} finally {
lock.unlock();
}
}
public static void printC() throws InterruptedException {
lock.lock();
try {
while (!CODE.equals("C")) {
conditionC.await();
}
System.out.println("C");
//改暗號(hào),通知A
CODE = "A";
conditionA.signalAll();
} finally {
lock.unlock();
}
}
}
效果:

可以看到,三個(gè)線程 ABC 都開始 無休止的進(jìn)行了 等待 -接通知 -核對(duì)- 打印-改暗號(hào)發(fā)通知 。
當(dāng)然如果需要他們不這么無休止,只需要 做一個(gè)標(biāo)識(shí)進(jìn)行判斷就好,例如 加在一起已經(jīng)打印夠100次了,就停止 之類的限制值。
舉例, 如果交替打印,到100 就停止, 也就是 從1~100 線程A ,線程B ,線程 B 交替打印。
ok,代碼稍作調(diào)整 :
加上2個(gè)值
一個(gè)是打印的數(shù)字,這個(gè)會(huì)一直 +1 輸出;
一個(gè)是用于線程循環(huán)的,之前是while(true) ,這樣會(huì)一直跑。

如果 終止標(biāo)記還是false,就繼續(xù)執(zhí)行:

每個(gè)打印方法都加上判斷和累計(jì)+1的代碼:

看看效果:

整體代碼貼一下:
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
import java.util.concurrent.locks.Condition;
public class DoTest {
//控制三個(gè)線程 ABC,保證同一時(shí)刻只有一個(gè)線程工作
private static Lock lock = new ReentrantLock(true);
// Condition ,控制等待或是 通知
private static Condition conditionA = lock.newCondition();
private static Condition conditionB = lock.newCondition();
private static Condition conditionC = lock.newCondition();
//默認(rèn)的通知 暗號(hào) A
private static String CODE = "A";
//設(shè)置初始打印值
private static int COUNT_NUM = 1;
//線程是否需要終止 標(biāo)記
private static volatile boolean IS_INTERRUPT = false;
public static void main(String[] args) {
Thread A = new Thread(() -> {
while (!IS_INTERRUPT) {
try {
printA();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
});
Thread B = new Thread(() -> {
while (!IS_INTERRUPT) {
try {
printB();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
});
Thread C = new Thread(() -> {
while (!IS_INTERRUPT) {
try {
printC();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
});
A.start();
B.start();
C.start();
}
public static void printA() throws InterruptedException {
//等待
lock.lock();
try {
if (COUNT_NUM >= 100) {
IS_INTERRUPT = true;
return;
}
//核對(duì)
while (!CODE.equals("A")) {
//暗號(hào)不對(duì),就進(jìn)入等待
conditionA.await();
}
System.out.println("A, count" + COUNT_NUM);
//改暗號(hào),通知B
CODE = "B";
COUNT_NUM = COUNT_NUM + 1;
conditionB.signalAll();
} finally {
lock.unlock();
}
}
public static void printB() throws InterruptedException {
lock.lock();
try {
if (COUNT_NUM >= 100) {
IS_INTERRUPT = true;
return;
}
while (!CODE.equals("B")) {
conditionB.await();
}
System.out.println("B, count" + COUNT_NUM);
//改暗號(hào),通知C
CODE = "C";
COUNT_NUM = COUNT_NUM + 1;
conditionC.signalAll();
} finally {
lock.unlock();
}
}
public static void printC() throws InterruptedException {
lock.lock();
try {
if (COUNT_NUM >= 100) {
IS_INTERRUPT = true;
return;
}
while (!CODE.equals("C")) {
conditionC.await();
}
System.out.println("C, count" + COUNT_NUM);
//改暗號(hào),通知A
CODE = "A";
COUNT_NUM = COUNT_NUM + 1;
conditionA.signalAll();
} finally {
lock.unlock();
}
}
}
可能看到這里,有些初學(xué)者會(huì)心有疑惑,因?yàn)樗麤]了解過 ReentrantLock、Lock 、Condition ,就覺得這個(gè)看了不踏實(shí)。
沒事的,其實(shí)只是為了給大家實(shí)打?qū)嵎治鏊悸罚?用synchronized 配合 wait() 和 notifyAll 也是一樣的。
notifyAll 大不了就全部通知喚醒,然后自己核對(duì)暗號(hào)再進(jìn)入等待。
示例,上代碼:
public class DoTest1 {
private volatile int COUNT_NUM = 1;
private volatile String CODE = "A";
private static int oneTimes = 34;
private static int othersTimes = 33;
void onePrint() {
synchronized (this) {
while(!CODE.equals("A")) {
try {
wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
System.out.println(Thread.currentThread().getName() + ": " + COUNT_NUM);
COUNT_NUM++;
CODE = "B";
notifyAll();
}
}
void twoPrint() {
synchronized (this) {
while(!CODE.equals("B")) {
try {
wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
System.out.println(Thread.currentThread().getName() + ": " + COUNT_NUM);
COUNT_NUM++;
CODE = "C";
notifyAll();
}
}
void threePrint() {
synchronized (this) {
while(!CODE.equals("C")) {
try {
wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
System.out.println(Thread.currentThread().getName() + ": " + COUNT_NUM);
COUNT_NUM++;
CODE = "A";
notifyAll();
}
}
public static void main(String[] args) {
DoTest1 printNumber = new DoTest1();
new Thread(() -> {
for (int i = 0; i < oneTimes; i++) {
printNumber.onePrint();
}
},"線程A").start();
new Thread(() -> {
for (int i = 0; i < othersTimes; i++) {
printNumber.twoPrint();
}
},"線程B").start();
new Thread(() -> {
for (int i = 0; i < othersTimes; i++) {
printNumber.threePrint();
}
},"線程C").start();
}
}
代碼簡(jiǎn)析:

看看效果:

好了,該篇就到這吧。