java程式設計思想之併發(執行緒之間的協作)

我是傳奇哈哈發表於2017-12-28

當你使用多執行緒來同時執行多個任務時,可以通過使用鎖來同步兩個任務的行為,從而使的一個任務不會干涉另一個任務的資源。也就是說,如果兩個任務交替的步入某項共享資源,你可以使用互斥來保證任何時刻只有一個任務可以訪問這項資源。

執行緒之間的協作

上面的問題已經解決了,下一步是如何使得任務彼此之間可以協作,使得多個任務可以一起工作去解決某個問題。現在的問題不是彼此之間的干涉,而是彼此之間的協作。解決這類問題的關鍵是某些部分必須在其他部分被解決之前解決。

當任務協作時,關鍵問題是這些任務之間的握手。為了實現握手,我們使用了相同的基礎特性:互斥。在這種情況下,互斥能夠確保只有一個任務可以響應某個訊號,這樣就能根除任何可能的競爭條件。在互斥上,我們為任務新增了一種途徑,可以將自身掛起,直至某些外部條件發生變化,表示是時候讓這個任務開始為止。

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 程式設計思想筆記

java程式設計思想之併發(執行緒之間的協作)

相關文章