1. 程式人生 > 實用技巧 >《Java併發程式設計實戰》讀書筆記——第4章 物件的組合

《Java併發程式設計實戰》讀書筆記——第4章 物件的組合

4.1、設計執行緒安全的類

在設計執行緒安全類的過程中,需要包含以下三個基本要素:

1)找出構成物件狀態的所有變數。

2)找出約束條件狀態變數的不變性條件。

3)建立物件狀態的併發訪問管理策略。

4.1.1、收集同步需求

如果不瞭解物件的不變性條件與後驗條件,那麼就不能確保執行緒安全性。要滿足在狀態變數的有效值或狀態轉換上的各種約束條件,就需要藉助於原子性與封裝性。

4.1.2、依賴狀態的操作

如果在某個操作中包含有基於狀態的先驗條件,那麼這個操作就稱為依賴狀態的操作。

4.1.3、狀態的所有權

4.2、例項封閉

將資料封裝在物件內部,可以將資料的訪問限制在物件的方法上,從而更容易確保執行緒在訪問資料時總能持有正確的鎖。封閉機制更易於構造執行緒安全的類,因為當封閉類的狀態時,在分析類的執行緒安全性時就無須檢查整個程式。

4.2.1java監視器模式

遵循java監視器模式的物件會把物件的所有可變狀態都封裝起來,並由物件自己的內建鎖來保護。下面的類通過一個私有的鎖來保護物件。

public class PrivateLock{
  private final Object myLock = new Object();
  @GuardedBy(“myLock”) Widget widget;

  void someMethod(){
    synchronized(myLock){
      //訪問或修改Widget的狀態
    }
  }
}

4.2.2、示例:車輛追蹤

 1
@ThreadSafe 2 3 public class MonitorVehicleTracker{ 4 5 @GuardedBy(“this”) 6 7 private final Map<String,MutablePoint> locations; 8 9 public MonitorVehicleTracker(Map<String,MutablePoint> locations){ 10 11 this.locations = deepCopy(locations); 12 13 } 14 15 public
synchronized Map<String,MutablePoint>getLocations(){ 16 17 return deepCopy(locations); 18 19 } 20 21 public synchronized MutablePoint getLocation(String id){ 22 23 MutablePoint loc = locations.get(id); 24 25 return loc == null ? null : new MutablePoint(loc); 26 27 } 28 29 public synchronized void setLocation(String id, int x, int y){ 30 31 MutablePoint loc = locations.get(id); 32 33 if(loc == null){ 34 35 throw new IllegalArgumentException(“No such ID: “+id); 36 37 } 38 39 loc.x = x; 40 41 loc.y = y; 42 43 } 44 45 private static Map<String,MutablePoint> deepCopy(Map<String,MutablePoint> m){ 46 47 Map<String,MutablePoint> result = new HashMap<String,MutablePoint>(); 48 49 for(String id : m.keyset()){ 50 51 result.put(id, new MutablePoint(m.get(id))); 52 53 } 54 55 return Collections.unmodifiableMap(result); 56 57 } 58 59 } 60 61 @NotThreadSafe 62 63 public class MutablePoint(){ 64 65 public int x, y; 66 67 public MutablePoint(){ 68 69 x = 0; 70 71 y = 0; 72 73 } 74 75 public MutablePoint(MutablePoint p){ 76 77 this.x = p.x; 78 79 this.y = p.y; 80 81 } 82 83 }
View Code

雖然MutablePoint不是執行緒安全的,但是追蹤器類是執行緒安全的,它所包含的Map物件和可變的MutablePoint物件都未曾釋出,因為該追蹤器的建構函式通過deepCopy方法進行深度拷貝,那麼追蹤器的locations只是一個副本或快照,不會收到執行緒不安全的類MutablePoint的影響。同時,對外發布locations時再次採用deepCopy方法進行深度拷貝,那麼對外發布的物件又是追蹤器的locations的再次副本,不會影響到追蹤器的locations。最後,對追蹤器的locations的檢視和修改都是採用內建鎖的,因此也是執行緒安全的。

4.3、執行緒安全性的委託

在無狀態的類中增加一個執行緒安全型別的域,得到的組合仍然是執行緒安全的。可以稱該類將執行緒安全性委託給執行緒安全型別的域來保證。

4.3.1、示例:基於委託的車輛追蹤器

@ThreadSafe

public class DelegatingVehicleTracker{

private final ConcurrentMap<String,Point> locations;

private final Map<String,Point> unmodifiableMap;

public DelegatingVehicleTracker(Map<String,Point> points){

locations = new ConcurrentMap<String,Point>(points);

unmodifiableMap = Collections.unmodifiableMap(locations);

}

public Map<String,Point> getLocations(){

return unmodifiableMap;

}

public Point getLocation(String id){

return locations.get(id);

}

public void setLocation(String id, int x, int y){

if(locations.replace(id, new Point(x,y))==null){

throw new IllegalArgumentException(“invalid vehicle name: “+id);

}

}

}

@Immutable

public class Point{

public final int x, y;

public Point(int x, int y){

this.x = x;

this.y = y;

}

}

如果採用MutablePoint類而不是Point類,就會破壞封裝性,因為getLocations會發佈一個指向可變狀態的引用。雖然getLocations返回的是一個umodifiableMap,不可以修改該Map,但是Map儲存的物件是可變的,可以對Map儲存的物件進行修改,這樣就不是執行緒安全的。該追蹤器將它的執行緒安全性委託給ConcurrentHashMap來保證的。該追蹤器的getLocations返回的是一個不可修改但是實時的車輛位置檢視,因為getLocations返回的就是經過unmodifiableMap封裝的原locations,所以會實時變化。如果需要返回一個快照,可以如下改造。

public Map<String,Point> getLocations(){

return Collections.unmodifiableMap(new HashMap<String,Point>(locations));

}

這裡通過new一個新的HashMap來作為快照。

4.3.2、獨立的狀態變數

我們還可以將執行緒安全性委託給多個狀態變數,只要這些變數是彼此獨立的,即組合而成的類並不會在其包含的多個狀態變數上增加任何不變性條件。

4.3.3、當委託失效時

如果一個類是由多個獨立且執行緒安全的狀態變數組成,並且在所有的操作中都不包含無效狀態轉換,那麼可以將執行緒安全性委託給底層的狀態。

4.3.4、釋出底層的狀態變數

如果一個狀態變數是執行緒安全的,並且沒有任何不變性條件來約束它的值,在變數的操作上也不存在任何不允許的狀態轉換,那麼就可以安全地釋出這個變數。

4.3.5、示例:釋出狀態的車輛追蹤

@ThreadSafe

Public class SafePoint{

@GuardedBy(“this”)

private int x, y;

private SafePoint(int[] a){

this(a[0], a[1]);

}

private SafePoint(int x, int y){

this.x = x;

this.y = y;

}

public synchronized int[] get(){

return new int[]{x, y};

}

public synchronized void set(int x,int y){

this.x = x;

this.y = y;

}

}

@ThreadSafe

public class PublishingVehicleTracker{

private final ConcurrentMap<String,SafePoint> locations;

private final Map<String,SafePoint> unmodifiableMap;

public DelegatingVehicleTracker(Map<String,SafePoint> locations){

this.locations = new ConcurrentMap<String,SafePoint>(locations);

unmodifiableMap = Collections.unmodifiableMap(this.locations);

}

public Map<String,Point> getLocations(){

return unmodifiableMap;

}

public Point getLocation(String id){

return locations.get(id);

}

public void setLocation(String id, int x, int y){

if(locations.replace(id, new Point(x,y))==null){

throw new IllegalArgumentException(“invalid vehicle name: “+id);

}

}

}

SafePoint類是執行緒安全可變的,getLocations返回的是底層Map物件的一個不可變副本,呼叫者不能增加或刪除車輛,但是可以改變通過修改返回Map中的SafePoint值來改變車輛的位置。

4.4、在現有的執行緒安全類中新增功能

4.4.1、客戶端加鎖

@NotThreadSafe

public class ListHelper<E>{

public List<E> list = Collections.synchronizedList(new ArrayList<E>());

public synchronized boolean putIfAbsent(E e){

boolean abesnt = !list.contains(x);

if(absent){

list.add(x);

}

return absent;

}

}

以上的方式不是執行緒安全的,因為putIfAbsent方法的鎖是ListHelper類的內建鎖,而list方法的鎖是自己的內建鎖,兩者不是同一個鎖,所以不是一個原子操作。改進如下:

@ThreadSafe

public class ListHelper<E>{

public List<E> list = Collections.synchronizedList(new ArrayList<E>());

pubic boolean putIfAbsent(E x){

synchronized(list){

boolean abesnt = !list.contains(x);

if(absent){

list.add(x);

}

return absent;

}

}

}

putIfAbsent方法採用list的內建鎖,這樣就和先檢查再插入的兩個操作的內建鎖保持同一個鎖了。

4.4.2、組合

@ThreadSafe

public class ImprovedList<T> implements List<T>{

private fianl List<T> list;

public ImporvedList(List<T> list){

this.list = list;

}

public synchronized boolean putIfAbsent(T x){

boolean contains = list.contains(x);

if(!contains){

list.add(x);

}

return !contains;

}

}

ImprovedList通過將List物件的操作委託給底層的List例項來實現相關的操作,還實現了putIfAbsent方法,ImprovedList並不關心底層的List是否執行緒安全,它通過自身的內建鎖增加來一層額外的鎖來實現執行緒安全。

4.5、將同步策略文件化

在文件中說明客戶程式碼需要了解的執行緒安全性保證,以及程式碼維護人員需要了解的同步策略。