Java并發(fā)編程之對象的組合
1. 設(shè)計線程安全的類
在設(shè)計線程安全類的過程中,需要包含以下三個基本要素:
- 找出構(gòu)成對象狀態(tài)的所有變量。
- 找出約束變量的不變性條件。
- 建立對象狀態(tài)的并發(fā)訪問管理策略。
1.1 收集同步需求
在很多類中都定義了一些不可變條件,用于判斷狀態(tài)是否有效。比如:計數(shù)器的取值范圍上存在一個限制,就是不能是負值。在操作中還會包含一些后驗條件來判斷狀態(tài)遷移是否有有效。比如:計數(shù)器的當前狀態(tài)為8,那么下一個有效狀態(tài)只能是9。由于不變性條件和后驗條件在狀態(tài)及狀態(tài)轉(zhuǎn)換上施加了各種約束,因此就需要額外的同步和封裝。
1.2 依賴狀態(tài)的操作
在某些對象的方法中還包含一些基于狀態(tài)的先驗條件。比如:不能從空隊列中移除一個元素,在刪除元素前,隊列必須處于非空狀態(tài)。如果在某個操作中包含有基于狀態(tài)的先驗條件,那么這個操作就稱為依賴狀態(tài)的操作。
1.3 狀態(tài)的所有權(quán)
對象封裝了它擁有的狀態(tài),那么它就擁有封裝狀態(tài)的所有權(quán)。狀態(tài)的所有者將決定采用何種加鎖協(xié)議來維持狀態(tài)的完整性。所有權(quán)意味著控制權(quán)。如果發(fā)布了某個可變對象的引用,那么就不再擁有獨占的控制權(quán),最多是共享控制權(quán)。為了防止多個線程在并發(fā)訪問同一個對象時產(chǎn)生相互干擾,這些對象應該要么是線程安全的對象,要么是事實不可變的對象,或者用同一個鎖保護的對象。
2. 實例封閉
將數(shù)據(jù)封裝在對象內(nèi)部,可以將數(shù)據(jù)的訪問限制在對象的方法上,從而更容易確保線程在訪問數(shù)據(jù)是總能持有正確的鎖。被封閉對象一定不能超過它們既定的作用域。對象可以封閉在類的一個實例中,比如:作為類的一個私有成員;或者封閉在某個作用域內(nèi),比如:作為一個局部變量;或者封閉在線程中,比如:在某個線程中將對象從一個方法傳遞到另一個方法,而不是在多個線程之間共享該對象。
封閉機制更易于構(gòu)造線程安全的類,因為當封閉類的狀態(tài)時,在分析類的線程安全性時就無須檢測整個程序。
2.1 Java監(jiān)視器模式
遵循Java監(jiān)視器模式的對象會把對象的所有可變狀態(tài)都封裝起來,并由對象自己的內(nèi)置鎖來保護。示例:一個用于調(diào)度車輛的“車輛跟蹤器”,每臺車都由一個String對象來標識,并且擁有一個對應的位置坐標(x,y)。創(chuàng)建一個追蹤器類用來封裝所有車輛的標識和位置,該類由多個線程(讀取操作和更新操作)共享。
@NotThreadSafe public class MutablePoint { public int x, y; public MutablePoint() { x = 0; y = 0; } public MutablePoint(MutablePoint p) { this.x = p.x; this.y = p.y; } } @ThreadSafe public class MonitorVehicleTracker { @GuardedBy("this") private final Map<String, MutablePoint> locations; public MonitorVehicleTracker(Map<String, MutablePoint> locations) { this.locations = deepCopy(locations); } public synchronized Map<String, MutablePoint> getLocations() { return deepCopy(locations); } public synchronized MutablePoint getLocation(String id) { MutablePoint loc = locations.get(id); return loc == null ? null : new MutablePoint(loc); } public synchronized void setLocation(String id, int x, int y) { MutablePoint loc = locations.get(id); if (loc == null) throw new IllegalArgumentException("No such ID: " + id); loc.x = x; loc.y = y; } private static Map<String, MutablePoint> deepCopy(Map<String, MutablePoint> m) { Map<String, MutablePoint> result = new HashMap<String, MutablePoint>(); for (String id : m.keySet()) result.put(id, new MutablePoint(m.get(id))); return Collections.unmodifiableMap(result); } }
雖然類MutablePoint
不是線程安全的,但是追蹤器類是線程安全的。它所包含的Map對象和可變的Point對象都未曾發(fā)布。當需要回去車輛的位置時,通過拷貝構(gòu)造函數(shù)或deepCopy方法復制正確的值,從而生成一個新的Map對象。車輛少是,這并不存在性能問,單在車輛數(shù)量非常大的情況下將極大地降低性能。
3. 線程安全性的委托
大多數(shù)對象都是組合對象。當從頭開始構(gòu)建一個類,或者將多個非線程安全的類組合為一個類時,Java監(jiān)視器模式是非常有用的。但是,如果類中的各個組件都已經(jīng)是線程安全的,那么實現(xiàn)線程安全就應視情況而定。
3.1 基于委托的車輛追蹤器
保存車輛位置信息的Map類,使用線程安全的ConcurrentMap
類代替。
使用不可變的Point類來代替MutablePoint類:
public class Point { public final int x, y; public Point(int x, int y) { this.x = x; this.y = y; } }
在DelegatingVehicleTracker中沒有使用顯式的同步,所有對狀態(tài)的訪問都由ConcurrentHashMap來管理:
@ThreadSafe public class DelegatingVehicleTracker { private final ConcurrentMap<String, Point> locations; private final Map<String, Point> unmodifiableMap; public DelegatingVehicleTracker(Map<String, Point> points) { locations = new ConcurrentHashMap<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); } }
在使用監(jiān)視器模式的車輛追蹤器中返回的是車輛位置的快照,而在使用委托的車輛追蹤器中返回的是一個不可修改卻實時的車輛位置信息。
3.2 獨立的狀態(tài)變量
我們還可以將線程安全性委托給多個狀態(tài)變量,只要這些變量是彼此獨立的,即組合而成的類并不會在其包含的多個狀態(tài)變量上增加任何不變形條件。
public class VisualComponent { private final List<KeyListener> keyListeners = new CopyOnWriteArrayList<KeyListener>(); private final List<MouseListener> mouseListeners = new CopyOnWriteArrayList<MouseListener>(); public void addKeyListener(KeyListener listener) { keyListeners.add(listener); } public void addMouseListener(MouseListener listener) { mouseListeners.add(listener); } public void removeKeyListener(KeyListener listener) { keyListeners.remove(listener); } public void removeMouseListener(MouseListener listener) { mouseListeners.remove(listener); } }
VisualComponent
類是一個圖形組件,允許客戶程序注冊監(jiān)控鼠標和鍵盤等事件的監(jiān)聽器。在鼠標事件監(jiān)聽器和鍵盤事件監(jiān)聽器之間不存在任何關(guān)聯(lián),二者是彼此獨立的,因此VisualComponent
可以將其線程安全性委托給這兩個線程安全的監(jiān)聽器列表。
如果一個類是由多個獨立且線程安全的狀態(tài)變量組成,并且在所有的操作中都不包含無效狀態(tài)轉(zhuǎn)換,那么可以將線程安全性委托給底層的狀態(tài)變量。
3.3 發(fā)布底層的狀態(tài)變量
如果一個狀態(tài)變量是線程安全的,并且沒有任何不變性條件來約束它的值,在變量的操作上也不存在任何不允許的狀態(tài)轉(zhuǎn)換,那么就可以安全地發(fā)布這個變量。我們來修改之前的車輛追蹤器的代碼,使其發(fā)布底層的可變狀態(tài)。
首先寫一個可變并且線程安全的Point類:
public class SafePoint { @GuardedBy("this") private int x, y; private SafePoint(int[] a) { this(a[0], a[1]); } public SafePoint(SafePoint p) { this(p.get()); } public SafePoint(int x, int y) { this.set(x, y); } public synchronized int[] get() { return new int[]{x, y}; } public synchronized void set(int x, int y) { this.x = x; this.y = y; } }
再修改追蹤器的代碼:
public class PublishingVehicleTracker { private final Map<String, SafePoint> locations; private final Map<String, SafePoint> unmodifiableMap; public PublishingVehicleTracker(Map<String, SafePoint> locations) { this.locations = new ConcurrentHashMap<String, SafePoint>(locations); this.unmodifiableMap = Collections.unmodifiableMap(this.locations); } public Map<String, SafePoint> getLocations() { return unmodifiableMap; } public SafePoint getLocation(String id) { return locations.get(id); } public void setLocation(String id, int x, int y) { if (!locations.containsKey(id)) throw new IllegalArgumentException("invalid vehicle name: " + id); locations.get(id).set(x, y); } }
在PublishingVehicleTracker
中,其線程安全性委托給底層的ConcurrentHashMap
,只是Map中的元素是線程安全的可變的。getLocations
方法返回底層Map對象的一個不可變副本,調(diào)用者不能增加或刪除車輛,但可以通過修改返回Map中的SafePoint
值來修改車輛的問題。
到此這篇關(guān)于Java并發(fā)編程之對象的組合的文章就介紹到這了,更多相關(guān)Java對象的組合內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
解決springmvc+mybatis+mysql中文亂碼問題
這篇文章主要介紹了解決java中springmvc+mybatis+mysql中文亂碼問題的相關(guān)資料,需要的朋友可以參考下2015-09-09java 輸入3個數(shù)a,b,c,按大小順序輸出的實例講解
今天小編就為大家分享一篇java 輸入3個數(shù)a,b,c,按大小順序輸出的實例講解,具有很好的參考價值,希望對大家有所幫助。一起跟隨小編過來看看吧2018-07-07java使用zookeeper實現(xiàn)的分布式鎖示例
這篇文章主要介紹了java使用zookeeper實現(xiàn)的分布式鎖示例,需要的朋友可以參考下2014-05-05idea創(chuàng)建springboot項目,Application.java不能運行問題及解決
這篇文章主要介紹了idea創(chuàng)建springboot項目,Application.java不能運行問題及解決方案,具有很好的參考價值,希望對大家有所幫助,如有錯誤或未考慮完全的地方,望不吝賜教2023-11-11