當你使用多執行緒來同時執行多個任務時,可以通過使用鎖來同步兩個任務的行為,從而使的一個任務不會干涉另一個任務的資源。也就是說,如果兩個任務交替的步入某項共享資源,你可以使用互斥來保證任何時刻只有一個任務可以訪問這項資源。
執行緒之間的協作
上面的問題已經解決了,下一步是如何使得任務彼此之間可以協作,使得多個任務可以一起工作去解決某個問題。現在的問題不是彼此之間的干涉,而是彼此之間的協作。解決這類問題的關鍵是某些部分必須在其他部分被解決之前解決。
當任務協作時,關鍵問題是這些任務之間的握手。為了實現握手,我們使用了相同的基礎特性:互斥。在這種情況下,互斥能夠確保只有一個任務可以響應某個訊號,這樣就能根除任何可能的競爭條件。在互斥上,我們為任務新增了一種途徑,可以將自身掛起,直至某些外部條件發生變化,表示是時候讓這個任務開始為止。
wait() 與 notifyAll()
wait() 可以使你等待某個條件發生變化,而改變這個條件通常是由另一個任務來改變。你肯定不想在你的任務測試這個條件的同時,不斷的進行空迴圈,這被稱為忙等待,是一種不良的 cpu 使用方式。因此 wait() 會在外部條件發生變化的時候將任務掛起,並且只有在 notif() 或 notifAll() 發生時,這個任務才會被喚醒並去檢查所發生的變化。因此,wait() 提供了一種在任務之間對活動同步的方式。
呼叫 sleep() 時候鎖並沒有被釋放,呼叫 yield() 也是一樣。當一個任務在方法裡遇到對 wait() 呼叫時,執行緒執行被掛起,物件的鎖被釋放。這就意味著另一個任務可以獲得鎖,因此在改物件中的其他 synchronized 方法可以在 wait() 期間被呼叫。因此,當你在呼叫 wait() 時,就是在宣告:“我已經做完了所有的事情,但是我希望其他的 synchronized 操作在條件何時的情況下能夠被執行”。
有兩種形式的 wait():
- 第一種接受毫秒作為引數:指再次暫停的時間。
- 在 wait() 期間物件鎖是被釋放的。
- 可以通過 notif() 或 notifAll(),或者指令到期,從 wait() 中恢復執行。
- 第二種不接受引數的 wait().
- 這種 wait() 將無線等待下去,直到執行緒接收到 notif() 或 notifAll()。
wait()、notif()以及 notifAll() 有一個比較特殊的方面,那就是這些方法是基類 Object 的一部分,而不是屬於 Thread 類。僅僅作為執行緒的功能卻成為了通用基類的一部分。原因是這些方法操作的鎖,也是所有物件的一部分。所以你可以將 wait() 放進任何同步控制方法裡,而不用考慮這個類是繼承自 Thread 還是 Runnable。實際上,只能在同步方法或者同步程式碼塊裡呼叫 wait()、notif() 或者 notifAll()。如果在非同步程式碼塊裡操作這些方法,程式可以通過編譯,但是在執行時會得到 IllegalMonitorStateException 異常。意思是,在呼叫 wait()、notif() 或者 notifAll() 之前必須擁有獲取物件的鎖。
比如,如果向物件 x 傳送 notifAll(),那就必須在能夠得到 x 的鎖的同步控制塊中這麼做:
synchronized(x){
x.notifAll();
}
複製程式碼
我們看一個示例:一個是將蠟塗到 Car 上,一個是拋光它。拋光任務在塗蠟任務完成之前,是不能執行其工作的,而塗蠟任務在塗另一層蠟之前必須等待拋光任務完成。
public class Car {
//塗蠟和拋光的狀態
private boolean waxOn = false;
//打蠟
public synchronized void waxed() {
waxOn = true;
notifyAll();
}
//拋光
public synchronized void buffed() {
waxOn = false;
notifyAll();
}
//拋光結束被掛起即將開始打蠟任務
public synchronized void waitForWaxing() throws InterruptedException{
while (waxOn == false) {
wait();
}
}
//打蠟結束被掛起即將開始拋任務
public synchronized void waitForBuffing() throws InterruptedException{
while (waxOn == true) {
wait();
}
}
}
複製程式碼
開始打蠟的任務:
public class WaxOn implements Runnable{
private Car car;
protected WaxOn(Car car) {
super();
this.car = car;
}
@Override
public void run() {
try {
while (!Thread.interrupted()) {
System.out.println("Wax one");
TimeUnit.MICROSECONDS.sleep(200);
//開始打蠟
car.waxed();
//當前任務被掛起
car.waitForBuffing();
}
} catch (InterruptedException e) {
// TODO Auto-generated catch block
System.out.println(" Exiting via interrupt");
}
System.out.println("Ending wax on task");
}
}
複製程式碼
開始拋光的任務:
public class WaxOff implements Runnable{
private Car car;
protected WaxOff(Car car) {
super();
this.car = car;
}
@Override
public void run() {
// TODO Auto-generated method stub
try {
while (!Thread.interrupted()) {
//如果還是在打蠟就掛起
car.waitForWaxing();
System.out.println("Wax off");
TimeUnit.MICROSECONDS.sleep(200);
//開始拋光
car.buffed();
}
} catch (InterruptedException e) {
// TODO Auto-generated catch block
System.out.println("Wxtiing via interrupt");
}
System.out.println("Ending wax off task");
}
}
複製程式碼
測試類:
public class WaxOmatic {
public static void main(String[] args) throws Exception{
// TODO Auto-generated method stub
Car car = new Car();
ExecutorService service = Executors.newCachedThreadPool();
service.execute(new WaxOff(car));
service.execute(new WaxOn(car));
//暫停2秒鐘
TimeUnit.SECONDS.sleep(1);
//關閉所有的任務
service.shutdownNow();
}
}
複製程式碼
執行結果:
/.....
Wax one
Wax off
Wax one
Wax off
Wax one
Wax off
Exiting via interrupt
Wxtiing via interrupt
Ending wax on task
Ending wax off task
複製程式碼
在 waitForWaxing() 中檢查 WaxOn 標誌,如果它是 false,那麼這個呼叫任務將會被掛起。這個行為發生在 synchronized 方法中這一點很重要。因為在這個方法中任務已經獲得了鎖。當你呼叫 wait() 時,執行緒被掛起,而鎖被釋放。釋放鎖是本質所在,因為為了安全的改變物件的狀態,其他某個任務就必須能夠獲得這個鎖。
WaxOn.run() 表示給汽車打蠟的第一個步驟,它執行他的操作:呼叫 sleep() 模擬打蠟的時間,然後告知汽車打蠟結束,並且呼叫 waitForWaxing(),這個方法會呼叫 wait() 掛起當前打蠟的任務。直到 WaxOff 任務呼叫這兩車的 buffed(),從而改變狀態並且呼叫 notfiAll() 重新喚醒為止。翻過來也是一樣的,在執行程式時,你可以看到控制權在兩個任務之間來回的傳遞,這兩個步驟過程在不斷的重複。
錯失的訊號
當兩個執行緒使用 notif()/wait() 或者 notifAll()/wait() 進行協作時,有可能會錯過某個訊號。假設執行緒 T1 是通知 T2 的執行緒,而這兩個執行緒都使用下面的方式實現:
T1:
synchronized(X){
//設定 T2 的一個條件
<setup condition for T2>
x.notif();
}
T2:
while(someCondition){
//Potit
synchronized(x){
x.wait();
}
}
複製程式碼
以上的例子假設 T2 對 someCondition 發現其為 true()。在執行 Potit 其中執行緒排程器可能切換到了 T1。而 T1 將會執行重新設定 condition,並且呼叫喚醒。當 T2 繼續執行時,以至於不能意識到條件已經發生變化,因此會盲目的進入 wait()。此時喚醒在之前已經呼叫過了,而 T2 將無限的等待下去喚醒的訊號。
解決該問題的方案是防止 someCondition 變數上產生競爭條件:
synchronized(x){
while(someCondition){
x.wait();
}
}
複製程式碼
notif() 與 notifAll()
可能有多個任務在單個 Car 物件上被掛起處於 wait() 狀態,因此呼叫 notifyAll() 比呼叫 notify() 更安全。使用 notify() 而不是 notifyAll() 是一種優化。使用 notify() 時,在眾多等待同一個鎖的任務中只有一個被喚醒,因此如果你希望使用 notify(),就必須保證被喚醒的是恰當的任務。另外使用 notify() ,所有任務都必須等待相同的條件,因為如果你有多個任務在等待不同的條件,那你就不會知道是否喚醒了恰當的任務。如果使用 notfiy(),當條件發生變化時,必須只有一個任務能從中收益。最後,這些限制對所有可能存在的子類都必須總起作用。如果這些規則任何一條不滿足都必須使用 notifyAll()。
在 Java 的執行緒機制中,有一個描述是這樣的:notifyAll() 將喚醒所有正在等待的任務。這是否意味著在程式中任何地方,任何處於 wait() 狀態中的任務都將被任何對 notifyAll() 的呼叫喚醒呢?在下面的例項中說明了情況並非如此,當 notifyAll() 因某個特定鎖被呼叫時,只有等待這個鎖的任務才會被喚醒:
public class Blocker {
synchronized void waitingCall() {
try {
while(!Thread.interrupted()) {
wait();
System.out.print(Thread.currentThread() + " ");
}
} catch(InterruptedException e) {
// OK to exit this way
}
}
synchronized void prod() {
notify();
}
synchronized void prodAll() {
notifyAll();
}
}
複製程式碼
建立任務 Task:
public class Task implements Runnable {
static Blocker blocker = new Blocker();
public void run() { blocker.waitingCall(); }
}
複製程式碼
建立任務 Task2:
public class Task2 implements Runnable {
// A separate Blocker object:
static Blocker blocker = new Blocker();
public void run() { blocker.waitingCall(); }
}
複製程式碼
測試類:
public class NotifyVsNotifyAll {
public static void main(String[] args) throws Exception{
ExecutorService service = Executors.newCachedThreadPool();
for (int i = 0; i < 3; i++) {
service.execute(new Task());
}
service.execute(new Task2());
Timer timer = new Timer();
timer.scheduleAtFixedRate(new TimerTask() {
boolean prod = true;
@Override
public void run() {
// TODO Auto-generated method stub
if (prod) {
System.out.println("notify");
Task.blocker.prod();
prod = false;
}else {
System.out.println("notifyAll");
Task.blocker.prodAll();
prod = true;
}
}
}, 400, 400);
TimeUnit.SECONDS.sleep(5);
timer.cancel();
System.out.println("Time cancle");
TimeUnit.MILLISECONDS.sleep(500);
System.out.println("Task2.blocker.prodAll() ");
Task2.blocker.prodAll();
TimeUnit.MILLISECONDS.sleep(500);
System.out.println("\nShutting down");
service.shutdownNow(); // Interrupt all tasks
}
}
複製程式碼
測試結果:
notify
Thread[pool-1-thread-2,5,main] notifyAll
Thread[pool-1-thread-2,5,main] Thread[pool-1-thread-3,5,main] Thread[pool-1-thread-1,5,main] notify
Thread[pool-1-thread-2,5,main] notifyAll
Thread[pool-1-thread-2,5,main] Thread[pool-1-thread-1,5,main] Thread[pool-1-thread-3,5,main] notify
Thread[pool-1-thread-2,5,main] notifyAll
Thread[pool-1-thread-2,5,main] Thread[pool-1-thread-3,5,main] Thread[pool-1-thread-1,5,main] notify
Thread[pool-1-thread-2,5,main] notifyAll
Thread[pool-1-thread-2,5,main] Thread[pool-1-thread-1,5,main] Thread[pool-1-thread-3,5,main] notify
Thread[pool-1-thread-2,5,main] notifyAll
Thread[pool-1-thread-2,5,main] Thread[pool-1-thread-3,5,main] Thread[pool-1-thread-1,5,main] notify
Thread[pool-1-thread-2,5,main] notifyAll
Thread[pool-1-thread-2,5,main] Thread[pool-1-thread-1,5,main] Thread[pool-1-thread-3,5,main] Time cancle
Task2.blocker.prodAll()
Thread[pool-1-thread-4,5,main]
Shutting down
複製程式碼
從上面輸出的結果可以看出,我們啟動了三個 Task 任務執行緒,一個 Task2 執行緒。使用 timer 做了一個定時器,每間隔 4 毫秒就輪換啟動 Task.blocker 的 notify() 和 notifyAll()方法。我們看到 Task 和 Task2 都有 Blocker 物件,他們呼叫 Blocker 物件的時候都會被阻塞。我們看到當呼叫 Task.prod() 的時候只有一個在等待鎖的任務被喚醒,其餘兩個繼續掛起。當呼叫 Task.prodAll() 的時候等待的三個執行緒都會被喚醒。當呼叫 Task2。prodAll() 的時候 只有 Task2 的執行緒任務被喚醒。其餘的三個 Task 任務繼續掛起。
生產者與消費者
請考慮這樣一種情況,在飯店有一個廚師和一個服務員。這個服務員必須等待廚師做好膳食。當廚師準備好時會通知服務員,之後服務員上菜,然後返回繼續等待。這是一個任務協作示例:廚師代表生產者,而服務員代表消費者。兩個任務必須在膳食被生產和消費時進行握手,而系統必須是以有序的方式關閉。
膳食類:
public class Meal {
private final int orderNum;
public Meal(int orderNum) { this.orderNum = orderNum; }
public String toString() { return "Meal " + orderNum; }
}
複製程式碼
服務生類:
public class WaitPerson implements Runnable {
private Restaurant restaurant;
public WaitPerson(Restaurant r) {
restaurant = r;
}
public void run() {
try {
while(!Thread.interrupted()) {
synchronized(this) {
while(restaurant.meal == null)
wait(); // ... for the chef to produce a meal
}
Print.print("Waitperson got " + restaurant.meal);
synchronized(restaurant.chef) {
restaurant.meal = null;
restaurant.chef.notifyAll(); // Ready for another
}
}
} catch(InterruptedException e) {
Print.print("WaitPerson interrupted");
}
}
}
複製程式碼
廚師類:
public class Chef implements Runnable {
private Restaurant restaurant;
private int count = 0;
public Chef(Restaurant r) {
restaurant = r;
}
public void run() {
try {
while(!Thread.interrupted()) {
synchronized(this) {
while(restaurant.meal != null)
wait(); // ... for the meal to be taken
}
if(++count == 10) {
Print.print("Out of food, closing");
restaurant.exec.shutdownNow();
}
Print.printnb("Order up! ");
synchronized(restaurant.waitPerson) {
restaurant.meal = new Meal(count);
restaurant.waitPerson.notifyAll();
}
TimeUnit.MILLISECONDS.sleep(100);
}
} catch(InterruptedException e) {
Print.print("Chef interrupted");
}
}
}
複製程式碼
測試類:
public class Restaurant {
Meal meal;
ExecutorService exec = Executors.newCachedThreadPool();
WaitPerson waitPerson = new WaitPerson(this);
Chef chef = new Chef(this);
public Restaurant() {
exec.execute(chef);
exec.execute(waitPerson);
}
public static void main(String[] args) {
new Restaurant();
}
}
複製程式碼
執行結果:
Order up! Waitperson got Meal 1
Order up! Waitperson got Meal 2
Order up! Waitperson got Meal 3
Order up! Waitperson got Meal 4
Order up! Waitperson got Meal 5
Order up! Waitperson got Meal 6
Order up! Waitperson got Meal 7
Order up! Waitperson got Meal 8
Order up! Waitperson got Meal 9
Out of food, closing
Order up! WaitPerson interrupted
Chef interrupted
複製程式碼
**使用顯示的 Lock 和 Condition 物件
在 java SE5 的類庫中還有額外的顯示工具。我們來重寫我們的打蠟和拋光類。使用互斥並允許任務掛起的基本類是 Condition,你可以通過在 Condition 上呼叫 await() 來掛起一個任務。當外部條件發生變化時,意味著某個任務應該繼續執行,你可以通過呼叫 signal() 來通知這個任務,從而喚醒一個任務,或者呼叫 signalAll() 來喚醒所有在這個 Condition 上被掛起的任務。(signalAll() 比 notifAll() 是更安全的方式)
下面是重寫版本:
class Car {
private Lock lock = new ReentrantLock();
private Condition condition = lock.newCondition();
private boolean waxOn = false;
public void waxed() {
lock.lock();
try {
waxOn = true; // Ready to buff
condition.signalAll();
} finally {
lock.unlock();
}
}
public void buffed() {
lock.lock();
try {
waxOn = false; // Ready for another coat of wax
condition.signalAll();
} finally {
lock.unlock();
}
}
public void waitForWaxing() throws InterruptedException {
lock.lock();
try {
while(waxOn == false)
condition.await();
} finally {
lock.unlock();
}
}
public void waitForBuffing() throws InterruptedException{
lock.lock();
try {
while(waxOn == true)
condition.await();
} finally {
lock.unlock();
}
}
}
class WaxOn implements Runnable {
private Car car;
public WaxOn(Car c) { car = c; }
public void run() {
try {
while(!Thread.interrupted()) {
printnb("Wax On! ");
TimeUnit.MILLISECONDS.sleep(200);
car.waxed();
car.waitForBuffing();
}
} catch(InterruptedException e) {
print("Exiting via interrupt");
}
print("Ending Wax On task");
}
}
class WaxOff implements Runnable {
private Car car;
public WaxOff(Car c) { car = c; }
public void run() {
try {
while(!Thread.interrupted()) {
car.waitForWaxing();
printnb("Wax Off! ");
TimeUnit.MILLISECONDS.sleep(200);
car.buffed();
}
} catch(InterruptedException e) {
print("Exiting via interrupt");
}
print("Ending Wax Off task");
}
}
public class WaxOMatic2 {
public static void main(String[] args) throws Exception {
Car car = new Car();
ExecutorService exec = Executors.newCachedThreadPool();
exec.execute(new WaxOff(car));
exec.execute(new WaxOn(car));
TimeUnit.SECONDS.sleep(5);
exec.shutdownNow();
}
}
複製程式碼
在 Car 的構造器中單個的 Lock 將產生一個 Condition 物件,這個物件被用來管理任務之間的通訊。但是這個 Condition 不包含任何有關處理狀態的資訊,因此你需要額外的表示處理狀態的資訊,即 Boolean waxOn。
生產者消費者與佇列
wait() 和 notifAll() 方法以一種非常低階的方式解決了任務的互操作的問題,即每次互動時都握手。許多時候我們可以使用同步佇列來解決協作的問題,同步佇列在任何時刻只允許一個任務插入或移除元素。在 Java.util.concurrent.BlockingQueue 介面中提供了這個佇列,這個介面有大量的標準實現。可以使用 LinkedBlockingQueue 他是一個無界佇列,還可以使用 ArrayBlockingQueue,它具有固定的尺寸,可以在它被阻塞之前向其中放置有限數量的元素。
如果消費者任務試圖從佇列中獲取物件,而該佇列為空時,那麼這些佇列就可以掛起這些任務,並且當有更多的元素可用時恢復這些消費任務。阻塞佇列可以解決非常大的問題,而其方式與 wait() 和 notifyAll() 相比,則簡單切可靠。
下面是一個簡單的測試,它將多個 LiftOff 物件執行序列化。消費者 LiftOffRunner 將每個 LiftOff 物件從 BlockIngQueue 中推出並直接執行。它通過顯示的呼叫 run() 而是用自己的執行緒來執行,而不是為每個任務啟動一個執行緒。
首先把之前寫過的 LiftOff 類貼出來:
public class LiftOff implements Runnable{
protected int countDown = 10; // Default
private static int taskCount = 0;
private final int id = taskCount++;
public LiftOff() {}
public LiftOff(int countDown) {
this.countDown = countDown;
}
public String status() {
return "#" + id + "(" +
(countDown > 0 ? countDown : "Liftoff!") + "), ";
}
public void run() {
while(countDown-- > 0) {
System.out.print(status());
Thread.yield();
}
}
}
複製程式碼
LiftOffRunner 類:
public class LiftOffRunner implements Runnable{
private BlockingQueue<LiftOff> rockets;
protected LiftOffRunner(BlockingQueue<LiftOff> rockets) {
super();
this.rockets = rockets;
}
public void add(LiftOff lo) {
try {
rockets.put(lo);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
System.out.println("新增失敗");
}
}
@Override
public void run() {
// TODO Auto-generated method stub
try {
while (!Thread.interrupted()) {
LiftOff rocket = rockets.take();
rocket.run();
}
} catch (InterruptedException e) {
// TODO Auto-generated catch block
System.out.println("執行中斷");
}
System.out.println("退出執行");
}
}
複製程式碼
最後是測試類:
public class TestBlockingQueues {
static void getkey(){
try {
new BufferedReader(new InputStreamReader(System.in)).readLine();
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
static void getkey(String message) {
Print.print(message);
getkey();
}
static void test(String msg,BlockingQueue<LiftOff> queue){
LiftOffRunner runner = new LiftOffRunner(queue);
Thread thread = new Thread(runner);
thread.start();
//啟動了,但是內容是空的,就一直掛起,等待有新的內容進去
for (int i = 0; i < 5; i++) {
runner.add(new LiftOff(5));
}
getkey("Press Enter "+ msg);
thread.interrupt();
}
public static void main(String[] args) {
test("LinkedBlockingQueue", new LinkedBlockingQueue<LiftOff>());
test("ArrayBlockingQueue", new ArrayBlockingQueue<>(3));
test("SynchronousQueue", new SynchronousQueue<>());
}
}
複製程式碼
吐司 BlockingQueue
下面是一個示例,每一臺機器都有三個任務:一個只做吐司、一個給吐司抹黃油、另一個在塗抹黃油的吐司上抹果醬。我們來示例如果使用 BlockIngQueue 來執行這個示例:
class Toast {
public enum Status { DRY, BUTTERED, JAMMED }
private Status status = Status.DRY;
private final int id;
public Toast(int idn) { id = idn; }
public void butter() { status = Status.BUTTERED; }
public void jam() { status = Status.JAMMED; }
public Status getStatus() { return status; }
public int getId() { return id; }
public String toString() {
return "Toast " + id + ": " + status;
}
}
class ToastQueue extends LinkedBlockingQueue<Toast> {}
class Toaster implements Runnable {
private ToastQueue toastQueue;
private int count = 0;
private Random rand = new Random(47);
public Toaster(ToastQueue tq) { toastQueue = tq; }
public void run() {
try {
while(!Thread.interrupted()) {
TimeUnit.MILLISECONDS.sleep(
100 + rand.nextInt(500));
// Make toast
Toast t = new Toast(count++);
print(t);
// Insert into queue
toastQueue.put(t);
}
} catch(InterruptedException e) {
print("Toaster interrupted");
}
print("Toaster off");
}
}
// Apply butter to toast:
class Butterer implements Runnable {
private ToastQueue dryQueue, butteredQueue;
public Butterer(ToastQueue dry, ToastQueue buttered) {
dryQueue = dry;
butteredQueue = buttered;
}
public void run() {
try {
while(!Thread.interrupted()) {
// Blocks until next piece of toast is available:
Toast t = dryQueue.take();
t.butter();
print(t);
butteredQueue.put(t);
}
} catch(InterruptedException e) {
print("Butterer interrupted");
}
print("Butterer off");
}
}
// Apply jam to buttered toast:
class Jammer implements Runnable {
private ToastQueue butteredQueue, finishedQueue;
public Jammer(ToastQueue buttered, ToastQueue finished) {
butteredQueue = buttered;
finishedQueue = finished;
}
public void run() {
try {
while(!Thread.interrupted()) {
// Blocks until next piece of toast is available:
Toast t = butteredQueue.take();
t.jam();
print(t);
finishedQueue.put(t);
}
} catch(InterruptedException e) {
print("Jammer interrupted");
}
print("Jammer off");
}
}
// Consume the toast:
class Eater implements Runnable {
private ToastQueue finishedQueue;
private int counter = 0;
public Eater(ToastQueue finished) {
finishedQueue = finished;
}
public void run() {
try {
while(!Thread.interrupted()) {
// Blocks until next piece of toast is available:
Toast t = finishedQueue.take();
// Verify that the toast is coming in order,
// and that all pieces are getting jammed:
if(t.getId() != counter++ ||
t.getStatus() != Toast.Status.JAMMED) {
print(">>>> Error: " + t);
System.exit(1);
} else
print("Chomp! " + t);
}
} catch(InterruptedException e) {
print("Eater interrupted");
}
print("Eater off");
}
}
public class ToastOMatic {
public static void main(String[] args) throws Exception {
ToastQueue dryQueue = new ToastQueue(),
butteredQueue = new ToastQueue(),
finishedQueue = new ToastQueue();
ExecutorService exec = Executors.newCachedThreadPool();
exec.execute(new Toaster(dryQueue));
exec.execute(new Butterer(dryQueue, butteredQueue));
exec.execute(new Jammer(butteredQueue, finishedQueue));
exec.execute(new Eater(finishedQueue));
TimeUnit.SECONDS.sleep(5);
exec.shutdownNow();
}
}
複製程式碼
這個示例中沒有任何顯示的同步,因為同步佇列和系統的設計隱式的管理了每片 Toast 在任何時刻都只有一個任務在操作。因為佇列的阻塞,使得處理過程將被自動掛起和恢復。
我的微信公號:Android開發吹牛皮
掃碼關注可免費獲得全套的 Java 程式設計思想筆記