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