題目描述:
三個線程分別打印A,B,C,要求這三個線程一起運(yùn)行,打印n次,輸出形如“ABCABCABC....”的字符串。
在看之前不妨先敲代碼試試,看似很簡單的問題可能代碼寫起來沒那么順利。
使用Semaphore:
public class PrintABC {
private int times;
private Semaphore semaphoreA =new Semaphore(1);
private Semaphore semaphoreB =new Semaphore(0);
private Semaphore semaphoreC =new Semaphore(0);
public PrintABC(int times) {
this.times=times;
}
public static void main(String[] args) {
PrintABC PrintABC =new PrintABC(10);
new Thread(PrintABC::printA).start();
new Thread(PrintABC::printB).start();
new Thread(PrintABC::printC).start();
}
public void printA() {
print("A",semaphoreA,semaphoreB);
}
public void printB() {
print("B",semaphoreB,semaphoreC);
}
public void printC() {
print("C",semaphoreC,semaphoreA);
}
public void print(String name,Semaphore current,Semaphore next) {
for(int i=0;i<times;i++) {
try {
current.acquire();
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
System.out.println(name);
next.release();
}
}
}
邏輯描述:
注意哦,semaphoreB和semaphoreC的值都為0,而semaphoreA的值為1。
也就是說semaphoreB.acquire();前沒有使用過semaphoreB.release()的話,調(diào)用的這個線程是會被阻塞的。那么這個時候,只有我們的線程A(假設(shè)調(diào)用printA的線程為線程A)能順利運(yùn)行了(因?yàn)橹挥芯€程A的emaphoreA為1)
線程A啟動,調(diào)用semaphoreA.acquire(),繼續(xù)運(yùn)行,打印A,調(diào)用semaphoreB.release(),此時線程B能動了……
如果A又獲得時間片,進(jìn)入了下一個for循環(huán)了呢?調(diào)用了semaphoreA.acquire();此時也會是阻塞狀態(tài),因?yàn)樯弦淮窝h(huán)acquire()的還沒有被release()掉。
C也是一樣的道理去分析,說多了更容易亂,建議大家在print方法上打個斷點(diǎn)調(diào)試一下就能輕松理解。
使用Lock
public class PrintABC2 {
private int times;
private int state;
private Lock lock = new ReentrantLock();
public PrintABC2(int times) {
this.times = times;
}
public void printA() {
print("A", 0);
}
public void printB() {
print("B", 1);
}
public void printC() {
print("C", 2);
}
public void print(String name, int stateNow) {
for (int i = 0; i < times;) {
lock.lock();
if (stateNow == state % 3) {
state++;
i++;
System.out.println(name);
}
lock.unlock();
}
}
public static void main(String[] args) {
PrintABC2 printABC2 = new PrintABC2(10);
new Thread(printABC2::printA).start();
new Thread(printABC2::printB).start();
new Thread(printABC2::printC).start();
}
}
邏輯描述:
在這個代碼中,三個線程會不斷的獲取lock,判斷stateNow == state % 3,而只有A線程進(jìn)入if內(nèi)的代碼段后,B線程才有機(jī)會執(zhí)行,否則只是獲取鎖,判斷為false,把鎖讓出來而已,沒有做實(shí)際的操作。C也是一樣
使用Condition
public class LockCoditionTest {
final Business business = new Business();
public static void main(String[] args) {
new LockCoditionTest().init();
}
private void init() {
new Thread(new Runnable() {
@Override
public void run() {
for (int i = 0; i <= 10; i++) {
business.sub2("B");
}
}
}).start();
new Thread(new Runnable() {
@Override
public void run() {
for (int i = 0; i <= 10; i++) {
business.sub3("C");
}
}
}).start();
for (int i = 0; i <= 10; i++) {
business.main("A");
}
}
class Business {
private int flag = 1;
Lock lock = new ReentrantLock();
Condition condition1 = lock.newCondition();
Condition condition2 = lock.newCondition();
Condition condition3 = lock.newCondition();
public void sub2(String s) {
lock.lock();
try {
if (flag != 2) {
condition2.await();
}
System.out.println("B線程輸出" + s);
flag = 3;
condition3.signal();
} catch (Exception e) {
e.printStackTrace();
} finally {
lock.unlock();
}
}
public void sub3(String s) {
lock.lock();
try {
if (flag != 3) {
condition3.await();
}
System.out.println("C線程輸出" + s);
flag = 1;
condition1.signal();
} catch (Exception e) {
e.printStackTrace();
} finally {
lock.unlock();
}
}
public void main(String s) {
lock.lock();
try {
if (flag != 1) {
condition1.await();
}
System.out.println("main線程輸出" + s);
flag = 2;
condition2.signal();
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
lock.unlock();
}
}
}
}
邏輯描述:
每個線程都調(diào)用了Business類里的方法,別以為我的main()沒有創(chuàng)建線程哦,他可是在主線程下運(yùn)行的,這次我們通過了三個condition和一個flag來實(shí)現(xiàn)線程間的通信。
首先flag為1,如果此時B或者C線程拿到了,就會進(jìn)入if判斷,把自己給await啦,然后咱們的A線程拿到,輸出A,把flag改成2,再把B線程喚醒,這個時候flag已經(jīng)是 2啦,b線程if判斷為false,就不await自個了,輸出B,在把flag改成3,把線程C喚醒。這個較于上面的lock來說,用了condition來更細(xì)粒度的操作線程,使得沒輪到他們工作的時候就進(jìn)入阻塞狀態(tài),解決了上面lock方法中幾個線程間不必要的上下文切換(沒輪到你就給我乖乖休息)