摘要:線程安全需求分析三個例子都是關(guān)于車輛追蹤的。他們使用了不同的方式來保證車輛追蹤類的線程安全性。值得注意的值文檔也是維護(hù)線程安全的重要組成部分。
每個例子后面有代碼,大家可以先把代碼粘出來或者開兩個頁面,先過一下例子的代碼,然后一邊看分析一遍看代碼,上下拖動看的話效果不好。
歡迎拍磚和補(bǔ)充。
線程安全需求分析三個例子都是關(guān)于車輛追蹤的。他們使用了不同的方式來保證車輛追蹤類的線程安全性。
我們知道,如果要寫一個線程安全類,那么首先得明確這個類關(guān)于線程安全的需求。
那么這個類的線程安全需求就是:
訪問線程要么能夠看到寫線程對location的x,y坐標(biāo)完整的寫入,要么看不到。不允許出現(xiàn)訪問線程只看到寫線程寫了其中一個坐標(biāo)。
比如線程A在訪問location時,只看到了B線程對location的x或者y坐標(biāo)的寫入,那就破壞了這個類的線程安全性。
例子1(MonitorVehicleTracker ) 對 線程不安全+可變 對象進(jìn)行實(shí)例封閉和加鎖作者使用了實(shí)例封閉+加鎖機(jī)制保證了MonitorVehicleTracker類的安全性。
實(shí)例封閉的意思就是將狀態(tài)的訪問路徑限制在對象內(nèi)部,
實(shí)例限制后,只要對這些狀態(tài)的訪問自始至終使用同一個鎖,就能保證其線程安全性。
MonitorVehicleTracker的唯一狀態(tài):locations,是一個HashMap對象,大家都知道它是可變的,也是線程不安全的。
構(gòu)造函數(shù)和getLocations都做了一次deepCopy。這兩個deepCopy都是必須的。deepCopy保證了將locations對象封裝在了MonitorVehicleTracker實(shí)例中,向外發(fā)布的只是一個拷貝的副本。想要訪問locations這個狀態(tài),只能通過MonitorVehicleTracker對象,而所有的訪問路徑,都加上了鎖。
deepCopy方法返回結(jié)果使用了Collections.unmodifiableMap(map):這里不使用UnmodifiableMap,而是只是用deepCopy的HashMap也是可以的,但是文檔一定寫清楚,返回的是deepCopy的Map。不然站在調(diào)用者的角度,如果對其進(jìn)行寫操作,就不能獲得期望的結(jié)果。
值得注意的值 文檔也是維護(hù)線程安全的重要組成部分。
就好比SynchronizedCollection的子類,這些同步容器在調(diào)用iterator方法時并沒有加鎖。
導(dǎo)致如果用戶需要讀寫一致,那么在迭代的時候必須加鎖,而且這個鎖必須是創(chuàng)建的SynchronizedXXX對象本身。
如果對讀寫一致沒那么敏感,那迭代的時候只需要處理一下ConcurrentModificationException即可。
這些都是在SynchronizedXXX文檔上寫的很清楚的。
由于MutablePoint是可變的,如果deepCopy在迭代時不對每一個location進(jìn)行復(fù)制map.put(k, new MutablePoint(v.x,v.y));而是使用map.put(k, v);那getLocations發(fā)布出去的所有l(wèi)ocation就存在線程安全風(fēng)險,因?yàn)樵谕獠科渌€程得到location之后有可能對其進(jìn)行更新。getLocation是同樣的道理,發(fā)布出去的MutablePoint一定是副本。
locations字段的final是不是必須的?我認(rèn)為不是,因?yàn)檫@里并不是希望把MonitorVehicleTracker變成不可變對象。
如果沒有final,那就必須注意,要使用安全的方式來發(fā)布MonitorVehicleTracker對象。
安全發(fā)布參見原書3.5節(jié)或者這個例子https://segmentfault.com/q/10...)
代碼public class MonitorVehicleTracker { private final Map例子2(DelegatingVehicleTracker)locations; public MonitorVehicleTracker(Map locations) { this.locations = deepCopy(locations); } public synchronized Map getLocations() { return deepCopy(locations); } public synchronized MutablePoint getLocation(String id) { MutablePoint location = locations.get(id); return location == null ? null : new MutablePoint(location.x, location.y); } public synchronized void setLocation(String id, int x, int y) { MutablePoint point = locations.get(id); if (point != null) { point.x = x; point.y = y; } else { throw new IllegalArgumentException("No such Id:" + id); } } private Map deepCopy(Map locations) { Map map = new HashMap<>(); locations.forEach((k, v) -> { map.put(k, new MutablePoint(v.x, v.y)); }); return Collections.unmodifiableMap(map); } } /** * 可變,線程不安全 */ class MutablePoint { public int x, y; public MutablePoint(int x, int y) { this.x = x; this.y = y; } }
此類對訪問locations的所有方法都沒有加鎖,而是通過使用線程安全的ConcurrentHashMap來保證DelegatingVehicleTracker的線程安全性。相當(dāng)于是把線程安全行委托給了ConcurrentHashMap。
getLocations使用了UnmodifiableMap作為視圖返回。如果不使用UnmodifiableMap而是直接返回locations行不行?
我認(rèn)為是可以的,畢竟locations是ConcurrentHashMap類型,它是線程安全的,并且作為DelegatingVehicleTracker的一個狀態(tài),并沒有什么約束條件,或者不允許有的狀態(tài)遷移操作。
這里使用UnmodifiableMap只是增強(qiáng)了封裝性,意味著,你想修改車輛位置,那必須通過DelegatingVehicleTracker對象上的方法來操作。
作者還提到另一種方法:下邊代碼中的getCopyedLocations方法。
這個方法和getLocations方法的區(qū)別是前者在不能夠?qū)崟r地反應(yīng)車輛位置的變化,而后者可以。
因?yàn)?b>Collections.unmodifiableMap(new HashMap<>(locations));在new HashMap時做了putAll.它將locations的所有元素淺復(fù)制了一份。所以當(dāng)locations有寫入操作時,HashMap并不能得知。
而Collections.unmodifiableMap(locations)是將locations的引用保存在了UnmodifiableMap中,
所以當(dāng)locations有寫入操作時,UnmodifiableMap可以立即看到。
public class DelegatingVehicleTracker { private final ConcurrentHashMap例子3(PublishVehicleTracker)locations; private final Map locationsView; public DelegatingVehicleTracker(Map points) { this.locations = new ConcurrentHashMap<>(points); this.locationsView = Collections.unmodifiableMap(this.locations); } public Map getLocations() { return locationsView; } public Map getCopyedLocations() { return Collections.unmodifiableMap(new HashMap<>(locations)); } public ImmutablePoint getLocation(String id) { return locations.get(id); } public void setLocation(String id, int x, int y) { if (locations.replace(id, new ImmutablePoint(x, y)) == null) { throw new IllegalArgumentException("No such id:" + id); } } } class ImmutablePoint { private final int x, y; public ImmutablePoint(int x, int y) { this.x = x; this.y = y; } }
此類和DelegatingVehicleTracker的區(qū)別:
使用了線程安全的SafePoint。
setLocation方法不再replace一個新構(gòu)造的ImmutablePoint。
因?yàn)镾afePoint和ConcurrentHashMap都是線程安全的,
所以這幾個方法都不需要額外的同步,或者復(fù)制,直接調(diào)用他們的修改狀態(tài)的方法是沒問題的。
代碼public class PublishVehicleTracker { private final ConcurrentHashMaplocations; private final Map locationsView; public PublishVehicleTracker(Map points) { this.locations = new ConcurrentHashMap<>(points); this.locationsView = Collections.unmodifiableMap(this.locations); } public Map getLocations() { return locationsView; } public SafePoint getLocation(String id) { return locations.get(id); } public void setLocation(String id, int x, int y) { if (!locations.contains(id)) { throw new IllegalArgumentException("No such id:" + id); } locations.get(id).setXY(x, y); } } class SafePoint { private int x, y; public SafePoint(int x, int y) { this.x = x; this.y = y; } public synchronized int[] getXY() { return new int[]{x, y}; } public synchronized void setXY(int x, int y) { this.x = x; this.y = y; } }
文章版權(quán)歸作者所有,未經(jīng)允許請勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。
轉(zhuǎn)載請注明本文地址:http://m.hztianpu.com/yun/67854.html
摘要:學(xué)習(xí)編程的本最佳書籍這些書涵蓋了各個領(lǐng)域,包括核心基礎(chǔ)知識,集合框架,多線程和并發(fā),內(nèi)部和性能調(diào)優(yōu),設(shè)計模式等。擅長解釋錯誤及錯誤的原因以及如何解決簡而言之,這是學(xué)習(xí)中并發(fā)和多線程的最佳書籍之一。 showImg(https://segmentfault.com/img/remote/1460000018913016); 來源 | 愿碼(ChainDesk.CN)內(nèi)容編輯 愿碼Slo...
摘要:在添加新項目時使用堆棧,將堆棧的頂部放在新項目之后。當(dāng)從堆棧中彈出一個項目時,在返回項目之前,您必須檢查另一個線程自操作開始以來沒有添加其他項目。比較和交換將堆棧的頭部設(shè)置為堆棧中舊的第二個元素,混合完整的數(shù)據(jù)結(jié)構(gòu)。 Abstract Treiber Stack Algorithm是一個可擴(kuò)展的無鎖棧,利用細(xì)粒度的并發(fā)原語CAS來實(shí)現(xiàn)的,Treiber Stack在 R. Kent T...
摘要:對于域,編譯器和處理器要遵守兩個重排序規(guī)則在構(gòu)造函數(shù)內(nèi)對一個域的寫入,與隨后把這個被構(gòu)造對象的引用賦值給一個引用變量,這兩個操作之間不能重排序。這個屏障禁止處理器把域的寫重排序到構(gòu)造函數(shù)之外。下一篇深入理解內(nèi)存模型七總結(jié) 與前面介紹的鎖和volatile相比較,對final域的讀和寫更像是普通的變量訪問。對于final域,編譯器和處理器要遵守兩個重排序規(guī)則: 在構(gòu)造函數(shù)內(nèi)對一個fi...
摘要:能夠異步的執(zhí)行任務(wù),并且通常管理一個線程池。這樣我們就不用手動的去創(chuàng)建線程了,線程池中的所有線程都將被重用。在之后不能再提交任務(wù)到線程池。它不使用固定大小的線程池,默認(rèn)情況下是主機(jī)的可用內(nèi)核數(shù)。 原文地址: Java 8 Concurrency Tutorial: Threads and Executors Java 5 初次引入了Concurrency API,并在隨后的發(fā)布版本中...
閱讀 2850·2021-09-24 09:47
閱讀 4466·2021-08-27 13:10
閱讀 3090·2019-08-30 15:44
閱讀 1358·2019-08-29 12:56
閱讀 2649·2019-08-28 18:07
閱讀 2700·2019-08-26 14:05
閱讀 2712·2019-08-26 13:41
閱讀 1327·2019-08-26 13:33