java併發編程(三): 對象的組合

對象的組合:

  • 如何將現有的線程安全組件,組合成咱們想要的更大規模的程序。

設計線程安全的類:

  • 設計線程安全類的三個要素:

       1.找出構成對象狀態的全部變量; java

       2.找出約束狀態變量的不變性條件; 安全

       3.創建對象狀態的併發訪問管理策略。 併發

收集同步需求:

  • 若是不瞭解對象的不變性條件後驗條件,那就不能確保線程安全性。要知足在狀態變量的有效值狀態轉換上的各類約束條件,就須要藉助原子性封裝性

依賴狀態的操做:

  • 若是在某個操做中包含有基於狀態的先驗條件,那麼這個操做就稱爲依賴狀態的操做。如在操做前,判斷當前狀態是否能夠進行當前操做。

狀態的全部權:

  • 全部權封裝性老是相互關聯的:對象封裝它擁有的狀態,即對它封裝的狀態擁有全部權,固然全部權能夠經過傳遞對象,變成共享全部權

實例封閉:

  • 將數據封裝在對象內部,能夠將數據的訪問限制在對象的方法上,從而更容易確保線程在訪問數據時總能持有正確的鎖。
/**
 * 這裏將mySet實例封閉在PersonSet中,
 * 儘管HashSet是非線程安全類,
 * 因爲mySet是私有且不會逸出的,
 * 咱們經過公共接口提供給外部訪問,但加上了PersonSet內置鎖保護synchronized,
 * 於是PersonSet是一個線程安全的類
 */
@ThreadSafe
public class PersonSet {
	private final Set<Person> mySet = new HashSet<>();
	
	public synchronized void addPerson(Person p){
		mySet.add(p);
	}
	
	public synchronized boolean containsPerson(Person p){
		return mySet.contains(p);
	}
}
  • 封閉機制更易於構造線程安全的類,由於當封閉類的狀態時,在分析類的線程安全性時就無須檢查整個程序。

Java監視器模式:

  • Java監視器模式的對象會把對象的全部可變狀態都封裝起來,並由對象本身的內置鎖來保護。如Vector, Hashtable等類;
  • 咱們也能夠經過私有鎖來代替內置鎖:
public class PrivateLock {
	private final Object lock = new Object();
	
	public void methodOne(){
		synchronized(lock){
			// do sth.
		}
	}
}

線程安全性的委託:

  • 多個線程安全的類組合成的類,不必定就是線程安全的。
/**
 * 委託的PersonSet
 * 將內部操做委託給線程安全的類SynchronizedSet
 * 從而自身也是線程安全的
 */
@ThreadSafe
public class DelegatingPersonSet {
	private final Set<Person> mySet = 
			Collections.synchronizedSet(new HashSet<Person>());
	
	public void addPerson(Person p){
		mySet.add(p);
	}
	
	public boolean containsPerson(Person p){
		return mySet.contains(p);
	}
}

獨立的狀態變量:

  • 咱們還能夠將線程安全性委託給多個狀態變量,只要這些狀態變量彼此獨立(不相關):

/**
 * 將線程安全性委託給多個彼此獨立的狀態變量
 * VisualComponent使用CopyOnWriteArrayList(線程安全)來保存監聽器列表
 * keyListeners, mouseListeners彼此獨立
 * 所以VisualComponent線程安全
 */
public class VisualComponent {
	private final List<KeyListener> keyListeners = 
			new CopyOnWriteArrayList<>();
	private final List<MouseListener> mouseListeners = 
			new CopyOnWriteArrayList<>();
	
	public void addKeyListener(KeyListener keyListener){
		keyListeners.add(keyListener);
	}
	
	public void removeKeyListener(KeyListener keyListener){
		keyListeners.remove(keyListener);
	}
	
	public void addMouseListener(MouseListener mouseListener){
		mouseListeners.add(mouseListener);
	}
	
	public void removeMouseListener(MouseListener mouseListener){
		mouseListeners.remove(mouseListener);
	}
}

當委託失效時:

  • 當類內部多個狀態變量,他們之間存在不變性條件,即便這些狀態變量各自是線程安全的,那麼該類不必定就線程安全:
/**
 * NumberRange不足以保護它的不變性條件
 * 併發環境下不安全
 */
 @NotThreadSafe
public class NumberRange {
	//不變性條件: lower <= upper
	private final AtomicInteger lower = new AtomicInteger();
	private final AtomicInteger upper = new AtomicInteger();
	
	public void setLower(int i){
		if (i > upper.get()){ //不安全的檢查
			throw new IllegalArgumentException("lower can't > upper");
		}
		lower.set(i);
	}
	
	public void setUpper(int i){
		if (i < lower.get()){ //不安全的檢查
			throw new IllegalArgumentException("lower can't > upper");
		}
		upper.set(i);
	}
}
  • 若是一個類是由多個獨立線程安全的狀態變量組成,而且在全部的操做中都不包含無效狀態轉換,那麼能夠將線程安全性委託給底層的狀態變量。

發佈底層的狀態變量:

  • 若是一個狀態變量是線程安全的,而且沒有任何不變性條件來約束它的值,在變量的操做上也不存在任何不容許的狀態轉換,那麼就能夠安全地發佈這個變量,例如發佈上面VisualComponent的keyListeners, mouseListeners。

在現有的線程安全類中添加功能:

  • 經過擴展類,來添加功能。
/**
 * 經過擴展實現非重複Vector
 */
public class NoRepeatVector<E> extends Vector<E> {
	public synchronized boolean putIfAbsent(E e){
		boolean exist = contains(e);
		if (!exist) 
			add(e);
		return exist;
	}
}

客戶端加鎖機制:

  • 客戶端加鎖:對於使用某個對象X的客戶端代碼,使用X自己用於保護其狀態的鎖來保護這段客戶端代碼。
/**
 * 這段客戶端代碼看似線程安全,
 * 但其實並不安全,由於鎖住的對象不正確,
 * 這裏僅是鎖住ListHelper對象,但list對象並無被鎖住,
 * 其餘客戶端仍可在不安全狀況下對list進行操做
 */
@NotThreadSafe
public class ListHelper<E> {
	public List<E> list = 
			Collections.synchronizedList(new ArrayList<E>());
	
	public synchronized boolean putIfAbsent(E x){
		boolean absent = !list.contains(x);
		if (absent)
			list.add(x);
		return absent;
	}
}
因此上面的代碼,咱們應該對list加鎖,而不是ListHelper對象:
@ThreadSafe
public class SafeListHelper<E> {
	public List<E> list = 
			Collections.synchronizedList(new ArrayList<E>());
	
	public boolean putIfAbsent(E x){
		synchronized (list) {
			boolean absent = !list.contains(x);
			if (absent)
				list.add(x);
			return absent;
		}
	}
}

組合:

  • 當爲現有的類添加一個原子操做時,有一種更好的方法:組合(Composition)。
/**
 * 經過組合實現"若沒有則添加" 下午4:48:42
 */
@ThreadSafe
public class improvedList<T> implements List<T> {
	private final List<T> list;

	public improvedList(List<T> list) {
		this.list = list;
	}
	
	public synchronized boolean putIfAbsent(T t){
		boolean absent = !list.contains(t);
		if (absent)
			list.add(t);
		return absent;
	}

	@Override
	public synchronized int size() {
		return list.size();
	}
       ...
}

將同步策略文檔化:

  • 在文檔中說明客戶代碼須要瞭解的線程安全性保證,以及代碼維護人員須要瞭解的同步策略

不吝指正。 ide

相關文章
相關標籤/搜索