java 多線程 異常情況 和 lock的簡易用法



/**
 * 說明: 其中一個任務產生偶數,而其他任務消費這些數字。
 * 這裏,消費者任務的唯一工作就是檢查偶數的有效性。
 * 
 * @create @author Henry @date 2016-11-24
 *
 */
/**
 * 首先,我們定義EvenChecker,即消費者任務,因爲它將在隨後所有的
 * 示例中被複用。爲了將EvenChecker與我們要試驗的各種類型的生成器
 * 解耦,我們將創建一個名爲IntGenerator的抽象類,它包含EvenChecker
 * 必須瞭解的必不可少的方法:即一個Next()方法,和一個可以執行撤銷的方法。
 * 這個類沒有實現Generator接口,因爲它必須產生一個int,而泛型不支持基本類型的參數
 * 
 * @create @author Henry @date 2016-11-24
 *
 */
public abstract class IntGenerator {
	private volatile boolean canceled = false;

	public abstract int next();
	
	public void cancel() {
		canceled = true;
	}

	public boolean isCanceled() {
		return canceled;
	}
}



import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
/**
 * 在本例中可以被撤銷的類不是Runnable,而所有依賴於IntGenerator對象的EvenChecker任務
 * 將測試它,以查看它是否已經被撤銷,正如你在run()中所見。通過這種方式,共享公共資源(IntGenerator)
 * 的任務可以觀察該資源的終止信號。這可以消除所謂競爭條件,即兩個或更多的任務競爭響應某個條件,
 * 因此產生衝突或不一致結果的情況。
 * 你必須仔細考慮並防範併發系統失敗的所有可能途徑,
 * 例如,一個任務不能依賴於另一個任務,因爲任務關閉的順序無法得到保證。
 * 這裏通過使任務依賴於非任務對象,我們可以消除潛在的競爭條件。
 * 
 * @create @author Henry @date 2016-11-24
 *
 */
public class EvenChecker implements Runnable{
	private IntGenerator generator;
	private final int id;
	
	public EvenChecker(IntGenerator generator, int id) {
		this.generator = generator;
		this.id = id;
	}
	/**
	 * EvenChecker任務總是讀取和測試從與其相關的IntGenerator返回的值。注意,
	 * 如果generator.isCanceled()爲true,則run()將返回,這將告知EvenChecker.test()
	 * 中的Executor該任務完成了。任何EvenChecker任務都可以在與其相關聯的IntGenerator
	 * 上調用cancel(),這將導致所有其他使用該IntGenerator的EvenChecker得體地關閉。
	 * 在後面個節中,你將看到Java包含的用於線程終止的各種更通用的機制。
	 * 
	 * @create @author Henry @date 2016-11-24
	 */
	@Override
	public void run() {
		while(!generator.isCanceled()){
			int val=generator.next();
			if(val%2!=0){
				System.out.println(val+" not even!");
				generator.cancel();//Cancels all EvenCheckers
			}
		}
	}
	/**
	 * test() 方法通過啓動大量使用相同的IntGenerator的EvenChecker,設置並執行對任何類型的
	 * IntGenerator的測試。如果IntGenerator引發失敗,那麼test()將報告它並返回,
	 * 否則,你必須按下Control-C來終止它。
	 * 
	 * @create @author Henry @date 2016-11-24
	 * @param gp
	 * @param count
	 */
	public static void test(IntGenerator gp,int count){
		System.out.println("Press Control-C to exit");
		ExecutorService exec=Executors.newCachedThreadPool();
		for (int i = 0; i < count; i++) 
			exec.execute(new EvenChecker(gp, i));
		exec.shutdown();
	}
	public static void test(IntGenerator gp){
		test(gp, 10);
	}
}



/**
 * 一個任務有可能在另一個任務執行第一個對currentEvenValue的遞增操作之後,
 * 但是沒有執行第二個操作之前,調用next()方法(即,代碼中被註釋爲
 * "Danger point here!"的地方)。這將使這個值處於"不恰當"的狀態。爲了證明這是可能發生的,
 * EvenChecker.test()創建了一組EvenChecker對象,以連續地讀取並輸出同一個
 * EvenGenerator,並測試檢查每個數值是否都是偶數。如果不是,就會報告錯誤,而程序也將關閉。
 * 
 * 
 * @create @author Henry @date 2016-11-24
 *
 */
public class EventGenerator extends IntGenerator{
	private int currentEvenValue=0;
	/**
	 * 如果你正在寫一個變量,它可能接下來將被另一個線程讀取,
	 * 或者正在讀取一個上次已經被另一個線程寫過的變量,那麼你必須使用同步,並且,
	 * 讀取線程都必須使用相同的監視器鎖同步。
	 * 
	 * @create @author Henry @date 2016-11-24
	 */
	@Override
	public int next() {//synchronized
		++currentEvenValue;//Danger point here!
		//Thread.yield();
		++currentEvenValue;
		return currentEvenValue;
	}
	/**
	 * 這個程序最終將失敗,因爲各個EvenChecker任務在EvenGenerator處於"不恰當的"狀態時,
	 * 仍能夠訪問其中的信息。但是,更加你使用的特定操作系統和其他實現細節,直到
	 * EvenCenerator完成多次循環之前,這個問題都不會被探測到。如果你希望更快地發現
	 * 失敗,可以嘗試着將對yield()的調用放置在第一個和第二個遞增操作之間。這只是併發程序的部分問題
	 * 如果失敗的概率非常低,那麼即使存在缺陷,它們也可能看起來是正確的 。
	 * 
	 * 有一點很重要,那就是要注意到遞增程序自身也需要多個步驟,並且在遞增過程中任務
	 * 可能會被線程機制掛起---也就是說,在Java中,遞增不是原子性的操作。因爲,如果
	 * 不保護任務,即使單一的遞增也是不安全的。
	 * 
	 * @create @author Henry @date 2016-11-24
	 * @param args
	 */
	public static void main(String[] args) {
		EvenChecker.test(new EventGenerator());
	}
}

用lock解決問題


import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;

/**
 * MutexEvenGenerator添加了一個被互斥調用的鎖,並使用lock()和unlock()方法
 * 在next()內部創建了臨界資源。當你在使用Lock對象時,將這裏所示的慣用法內部化是很重要的:
 * 緊接着對lock()的調用,你必須放置在finally子句中帶有unlock()的try-finally語句中。
 * 注意,return語句必須在try子句中出現,以確保unlock()不會過早發生,從而將數據暴露給了
 * 第二個任務。
 * 
 * @create @author Henry @date 2016-11-24
 *
 */
public class MutexEvenGenerator extends IntGenerator {
	private int currentEvenValue = 0;
	private Lock lock=new ReentrantLock();
	/**
	 * 儘管try-finally所需的代碼比synchronized關鍵字要多,但是
	 * 這也代表了顯示的Lock對象的優點之一。如果使用synchronized關鍵字時,
	 * 某些事物失敗了,那麼就會拋出一個異常。但是你沒有機會去做任何清理工作,
	 * 以維護系統使其處於良好狀態。有了顯式的Lock對象,你就可以使用finally
	 * 子句將系統維護在正確的狀態了。
	 * 
	 * 大體上,當你使用sunchronized關鍵字時,需要寫的代碼量更少,並且用戶錯誤出現
	 * 的可能性也會降低,因爲通常只有在解決特殊問題時,才使用顯式的Lock對象。
	 * 例如,用synchronized關鍵字不能嘗試着獲取鎖且最終獲取鎖會失敗,或者嘗試着
	 * 獲取鎖一段時間,然後放棄它,要實現這些,你必須使用concurrent類庫。
	 * 
	 * 
	 * @create @author Henry @date 2016-11-24
	 */
	@Override
	public int next() {
		lock.lock();
		try{
			++currentEvenValue;//Danger point here!
			Thread.yield();
			++currentEvenValue;
			return currentEvenValue;
		}finally{
			lock.unlock();
		}
	}
	public static void main(String[] args) {
		EvenChecker.test(new MutexEvenGenerator());
	}

}

import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.ReentrantLock;

/**
 * ReentrantLock 允許你嘗試着獲取單最終未獲取鎖,這樣如果其他人已經獲取了這個鎖,那你
 * 就可以決定離開去執行其他一些事情,而不是等待直到這個鎖被釋放,就像在untimed()方法中
 * 所看到的。在timed()中,作出了嘗試去嘗試獲取鎖,該嘗試可以在兩秒之後失敗(注意,使用
 * 了java se5 的TimeUnit類來指定時間單位)。在main中,作爲匿名類而創建了一個單位的Thread,
 * 它將獲取鎖,這使得untimed()和timed()方法對某些事物將產生競爭。
 * 
 * 顯示的lock對象在枷鎖和釋放鎖方面,相對於內建的synchronized鎖來說,還賦予了你更細粒度的
 * 控制力。這對於實現專有同步結構是很有用的,例如用於遍歷鏈接列表中的節點,節節傳遞的枷鎖機制
 * (也稱爲鎖耦合),這種遍歷代碼必須在釋放當前節點的鎖之前不活下一個節點鎖。
 * 
 * @create @author Henry @date 2016-11-28
 *
 */
public class AttemptLocking {
	private ReentrantLock lock = new ReentrantLock();

	public void untimed() {
		boolean captured = lock.tryLock();
		try {
			System.out.println("tryLock():" + captured);
		} finally {
			if (captured)
				lock.unlock();
			System.out.println("untimed over");
		}
	}

	public void timed() {
		boolean captured = false;
		try {
			captured = lock.tryLock(2, TimeUnit.SECONDS);
		} catch (InterruptedException e) {
			throw new RuntimeException(e);
		}
		try {
			System.out.println("tryLock(2, TimeUnit.SECONDS):" + captured);
		} finally {
			if (captured)
				lock.unlock();
			System.out.println("timed over");
		}
	}

	public static void main(String[] args) {
		final AttemptLocking al = new AttemptLocking();
		al.untimed();// True --lock is available
		al.timed();// True -- lock is available
		// Now create a separate task to grab the lock;
		new Thread() {
			{
				//setDaemon(true);
			}
			public void run(){
				al.lock.lock();
				System.out.println("acquired");
			}
		}.start();
		Thread.yield(); //Give the 2nd tesk a chance
		al.untimed();	//False --lock grabbed by task
		al.timed();	//False --Lock grabbed by task
	}
}


發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章