1. 程式人生 > >執行緒同步工具(六)控制併發階段性任務的改變

執行緒同步工具(六)控制併發階段性任務的改變

宣告:本文是《 Java 7 Concurrency Cookbook 》的第三章, 作者: Javier Fernández González 譯者:鄭玉婷

控制併發階段性任務的改變

Phaser 類提供每次phaser改變階段都會執行的方法。它是 onAdvance() 方法。它接收2個引數:當前階段數和註冊的參與者數;它返回 Boolean 值,如果phaser繼續它的執行,則為 false;否則為真,即phaser結束執行並進入 termination 狀態。

如果註冊參與者為0,此方法的預設的實現值為真,要不然就是false。如果你擴充套件Phaser類並覆蓋此方法,那麼你可以修改它的行為。通常,當你要從一個phase到另一個,來執行一些行動時,你會對這麼做感興趣的。

在這個指南,你將學習如何控制phaser的 phase的改變,通過實現自定義版本的 Phaser類並覆蓋 onAdvance() 方法來執行一些每個phase 都會改變的行動。你將要實現一個模擬測驗,有些學生要完成他們的練習。全部的學生都必須完成同一個練習才能繼續下一個練習。

準備

指南中的例子是使用Eclipse IDE 來實現的。如果你使用Eclipse 或者其他的IDE,例如NetBeans, 開啟並建立一個新的java專案。

怎麼做呢

按照這些步驟來實現下面的例子::

package tool;

import java.util.Date;
import java.util.concurrent.Phaser;
import java.util.concurrent.TimeUnit;

//1.   建立一個類,名為 MyPhaser,並特別的擴充套件 Phaser 類。
public class MyPhaser extends Phaser {

	// 2. 覆蓋 onAdvance() 方法。根據 phase 的屬性的值,我們將呼叫不同的輔助方法。如果 phase 等於 0,呼叫
	// studentsArrived() 方法;又如果 phase 等於 1,呼叫 finishFirstExercise() 方法;又如果 phase
	// 等於 2,呼叫 finishSecondExercise() 方法;再如果 phase 等於 3,呼叫 finishExam()
	// 方法。否則,返回真值,表示phaser已經終結。
	@Override
	protected boolean onAdvance(int phase, int registeredParties) {
		switch (phase) {
		case 0:
			return studentsArrived();
		case 1:
			return finishFirstExercise();
		case 2:
			return finishSecondExercise();
		case 3:
			return finishExam();
		default:
			return true;
		}
	}

	// 3. 實現輔助方法 studentsArrived()。它在操控臺寫2條資訊,並返回false值來表明phaser將繼續執行。
	private boolean studentsArrived() {
		System.out.printf("Phaser: The exam are going to start. The students are ready.\n");
		System.out.printf("Phaser: We have %d students.\n",
				getRegisteredParties());
		return false;
	}

	// 4. 實現輔助方法 finishFirstExercise()。它在操控臺寫2條資訊,並返回false值來表明phaser將繼續執行。
	private boolean finishFirstExercise() {
		System.out.printf("Phaser: All the students have finished the first exercise.\n");
		System.out.printf("Phaser: It's time for the second one.\n");
		return false;
	}

	// 5. 實現輔助方法 finishSecondExercise()。它在操控臺寫2條資訊,並返回false值來表明phaser將繼續執行。
	private boolean finishSecondExercise() {
		System.out.printf("Phaser: All the students have finished the second exercise.\n");
		System.out.printf("Phaser: It's time for the third one.\n");
		return false;
	}

	// 6. 實現輔助方法 finishExam()。它在操控臺寫2條資訊,並返回false值來表明phaser將繼續執行。
	private boolean finishExam() {
		System.out.printf("Phaser: All the students have finished the exam.\n");
		System.out.printf("Phaser: Thank you for your time.\n");
		return true;
	}

	// 7. 建立一個類,名為 Student,並一定實現 Runnable 介面。這個類將模擬測驗的學生。
	public class Student implements Runnable {

		// 8. 宣告 a Phaser 物件,名為 phaser.
		private Phaser phaser;

		// 9. 實現類的建構函式,初始 Phaser 物件。
		public Student(Phaser phaser) {
			this.phaser = phaser;
		}

		// 10. 實現 run() 方法,模擬真實測驗。
		@Override
		public void run() {

			// 11. 首先,方法寫一條資訊到操控臺表明學生到達考場並呼叫 phaser 的 arriveAndAwaitAdvance()
			// 方法來等待其他執行緒們。
			System.out.printf("%s: Has arrived to do the exam. %s\n", Thread
					.currentThread().getName(), new Date());
			phaser.arriveAndAwaitAdvance();

			// 12. 然後,寫資訊到操控臺,呼叫私有 doExercise1() 方法模擬第一場測驗,寫另一條資訊到操控臺並呼叫 phaser
			// 的 arriveAndAwaitAdvance() 方法來等待其他學生結束第一場測驗。
			System.out.printf("%s: Is going to do the first exercise. %s\n",
					Thread.currentThread().getName(), new Date());
			doExercise1();
			System.out.printf("%s: Has done the first exercise. %s\n", Thread
					.currentThread().getName(), new Date());
			phaser.arriveAndAwaitAdvance();

			// 13. 為第二場和第三場實現相同的程式碼。
			System.out.printf("%s: Is going to do the second exercise.%s\n",
					Thread.currentThread().getName(), new Date());
			doExercise2();
			System.out.printf("%s: Has done the second exercise. %s\n", Thread
					.currentThread().getName(), new Date());
			phaser.arriveAndAwaitAdvance();
			System.out.printf("%s: Is going to do the third exercise. %s\n",
					Thread.currentThread().getName(), new Date());
			doExercise3();
			System.out.printf("%s: Has finished the exam. %s\n", Thread
					.currentThread().getName(), new Date());
			phaser.arriveAndAwaitAdvance();
		}

		// 14. 實現輔助方法 doExercise1()。此方法讓執行緒隨機休眠一段時間。
		private void doExercise1() {
			try {
				long duration = (long) (Math.random() * 10);
				TimeUnit.SECONDS.sleep(duration);
			} catch (InterruptedException e) {
				e.printStackTrace();
			}
		}

		// 15. 實現輔助方法 doExercise2()。此方法讓執行緒隨機休眠一段時間。
		private void doExercise2() {
			try {
				long duration = (long) (Math.random() * 10);
				TimeUnit.SECONDS.sleep(duration);
			} catch (InterruptedException e) {
				e.printStackTrace();
			}
		}

		// 16. 實現輔助方法 doExercise3()。此方法讓執行緒隨機休眠一段時間。
		private void doExercise3() {
			try {
				long duration = (long) (Math.random() * 10);
				TimeUnit.SECONDS.sleep(duration);
			} catch (InterruptedException e) {
				e.printStackTrace();
			}
		}
	}
}

實現例子的main類,建立名為Main的類並新增main() 方法。

package tool;

import tool.MyPhaser.Student;

//17.  實現例子的main類,建立名為Main的類並新增main() 方法。
public class Main {

	public static void main(String[] args) {

		// 18. 建立 MyPhaser物件。
		MyPhaser phaser = new MyPhaser();

		// 19. 建立5個 Student 物件並使用register()方法在phaser中註冊他們。
		MyPhaser.Student students[] = new Student[5];
		for (int i = 0; i < students.length; i++) {
			students[i] = phaser.new Student(phaser);
			phaser.register();
		}

		// 20. 建立5個執行緒來執行students並開始它們。
		Thread threads[] = new Thread[students.length];
		for (int i = 0; i < students.length; i++) {
			threads[i] = new Thread(students[i], "Student " + i);
			threads[i].start();
		}

		// 21. 等待5個執行緒的終結。
		for (int i = 0; i < threads.length; i++) {
			try {
				threads[i].join();
			} catch (InterruptedException e) {
				e.printStackTrace();
			}
		}

		// 22. 呼叫isTerminated()方法來寫一條資訊表明phaser是在termination狀態。
		System.out.printf("Main: The phaser has finished: %s.\n",
				phaser.isTerminated());
	}
}

它是怎麼工作的…

這個練習模擬了有3個測驗的真實測試。全部的學生必須都完成同一個測試才能開始下一個測試。為了實現這個必須使用同步,我們使用了Phaser類,但是你實現了你自己的phaser通過擴充套件原來的類,並覆蓋onAdvance() 方法.

在階段改變之前和在喚醒 arriveAndAwaitAdvance() 方法中休眠的全部執行緒們之前,此方法被 phaser 呼叫。這個方法接收當前階段數作為引數,0是第一個phase ,還有註冊的參與者數。最有用的引數是actual phase。如果你要基於不同的當前階段執行不同的操作,那麼你必須使用選擇性結構(if/else 或 switch)來選擇你想執行的操作。例子裡,我們使用了 switch 結構來為每個phase的改變選擇不同的方法。

onAdvance() 方法返回 Boolean 值表明 phaser 終結與否。如果返回 false 值,表示它還沒有終結,那麼執行緒將繼續執行其他phases。如果phaser 返回真值,那麼phaser將叫醒全部待定的執行緒們,並且轉移phaser到terminated 狀態,所以之後的任何對phaser的方法的呼叫都會被立刻返回,還有isTerminated() 方法將返回真值。

在核心類,當你建立 MyPhaser 物件,在phaser中你不用表示參與者的數量。你為每個 Student 物件呼叫了 register() 方法建立了phaser的參與者的註冊。這個呼叫不會在Student 物件或者執行它的執行緒與phaser之間這個建立任何關係。 說真的,phaser的參與者數就是個數字而已。phaser與參與者之間沒有任何關係。

下面的裁圖展示了例子的執行結果:

你可以發現學生們結束第一個練習的時間是不同的。當全部都結束練習時,phaser 呼叫onAdvance() 方法寫資訊到操控臺,接著全部的學生在同一時間開始第二場測試。

參見