生活随笔
收集整理的這篇文章主要介紹了
java多线程3.设计线程安全类
小編覺得挺不錯的,現在分享給大家,幫大家做個參考.
實例封閉:將數據封裝在對象中,將數據的訪問限制在對象的方法上,確保線程在訪問數據時總能持有正確的鎖
java平臺的類庫中有很多線程封閉的示例,其中一些類的唯一用途就是將非線程安全的類轉為線程安全的類。一些基本的容器類并非線程安全,如ArrayList和HashMap,但類庫提供了包裝器工廠方法,如Collections.synchronizedList,使這些非線程安全的類可以在多線程環境中安全地使用。
這些工廠方法通過“裝飾器”模式將容器類封裝在一個同步的封裝器對象中,包裝器將接口中的每個方法都實現為同步方法,并將調用請求轉發到底層的容器對象上。只要包裝器對象擁有對底層容器對象的唯一引用,那么它就是線程安全的。
?遵循java監視器模式的對象會把對象的所有可變狀態都封裝起來,并由對象自己的內置鎖來保護,其主要優勢在于它的簡單性。
?監視器模式僅僅是一種編寫代碼的約定,對于任何一種鎖對象,只要自始至終都使用該鎖對象,都可以用來保護對象的狀態。
private final Object lock =
new Object();public void doSomething(){synchronized(lock){//...
}} 私有的鎖可以將鎖封裝起來,使客戶代碼無法獲得鎖,但客戶代碼可以通過公有方法訪問鎖,以便參與到它的同步策略
?
/*** 每一臺車都由一個相應的String對象來標識,并擁有一個相應的位置坐標(x,y)雖然Point并不是線程安全的,但追蹤器類是線程安全的。* 它所包含的可變的Point對象和Map對象都未曾發布。這種實現方式是通過返回客戶代碼之前復制可變的數據來維持線程的安全性,* 這在車輛容器非常大的情況下將極大的降低性能。此外,由于每次調用getLocation就要復制數據,將出現一種錯誤情況。* 雖然車輛的實際位置發生了變化,但返回的信息卻保持不變。(線程T1獲取位置是通過復制原對象,在線程T2更新是通過替換原對象,T2的替換操作對于T1是未知的)*/
public class Demo{private final Map<String,Point>
locations;public Demo(Map<String,Point>
locations){this.locations =
deepCopy(locations);}public synchronized Map<String,Point>
getLocations(){return deepCopy(locations);}public synchronized Point getLocation(String key){Point p =
locations.get(key);return p ==
null ?
null :
new Point(p);}public synchronized void setLocation(String key,
int x,
int y){Point p =
locations.get(key);if(p ==
null){throw new IllegalArgumentException("no such id" +
key);}p.x =
x;p.y =
y;}private static Map<String,Point> deepCopy(Map<String,Point>
locations){Map<String,Point> copy =
new HashMap<String,Point>
();for(Entry<String,Point>
entry : locations.entrySet()){copy.put(entry.getKey(), entry.getValue());}return copy;}
}class Point{public int x;public int y;public Point(){x = 0
;y = 0
;}public Point(Point p){this.x =
p.x;this.y =
p.y;}
} /*** 將車輛的位置保存到一個Map對象中,首先實現一個線程安全的Map類,ConcurrentHashMap。* 在使用監視器模式的車輛追蹤器中返回的是車輛位置的快照,而在使用委托的車輛追蹤器中返回的是一個不可修改但卻實時的車輛位置。* 線程T1調用getLocations,線程T2隨后更新了某些點的位置。那么在返回給線程T1的Map中將反映出來,這樣能獲取更新的數據。* 但同時也可能導致不一致的車輛位置視圖,具體視需求而定。另外,Point類是不可變的,因而它是線程安全的。* 不可變的值可以被自由地共享與發布,因此返回location時不需要復制。如果是可變對象,getLocation會發布一個指向可變對象的非線程安全的引用。*/
public class Demo{private final ConcurrentMap<String,Point>
locations;private final Map<String,Point>
unmodifiableMap;public Demo(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 key){return locations.get(key);}public void setLocation(String key,
int x,
int y){if(locations.replace(key,
new Point(x,y)) ==
null){throw new IllegalArgumentException("no such id" +
key);}}
}class Point{public final int x;public final int y;public Point(
int x,
int y){this.x =
x;this.y =
y;}
} 示例中將線程安全委托給單個線程安全的變量(集合),其實還可以將線程安全性委托給多個狀態變量,只要這些變量是相互獨立的,即組合而成的類不會在其包含的多個狀態變量上增加任何不可變條件。因此如果底層變量是線程安全且彼此獨立,則可以發布底層的狀態變量,即將線程安全性委托給底層狀態變量。
/*** 調用者不能增加或刪除車輛,但可以通過修改返回Map中的Point值來改變車輛的位置。* Point的同步方法get()可以同時獲取x和y的值,如果為x和y分別提供get方法,那么在獲得這兩個不同坐標的操作之間,* x和y的值可能發生變化,從而導致調用者看到不一致的值。* 另外在拷貝方法Point(Point p)中實現為this(p.get()),而不是this(p.x,p.y),可以避免這種競態條件。*/
public class Demo{private final ConcurrentMap<String,Point>
locations;private final Map<String,Point>
unmodifiableMap;public Demo(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 key){return locations.get(key);}public void setLocation(String key,
int x,
int y){if(!
locations.containsKey(key)){throw new IllegalArgumentException("no such id" +
key);}locations.get(key).set(x, y);}
}class Point{private int x;private int y;private Point(
int[] a){this(a[0],a[1
]);}public Point(Point p){this(p.get());}public Point(
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;}
} ?
- 提問:若要添加一個新的原子操作 “若沒有則添加”
- 1. 最安全的方法是修改原始的類,但這通常無法做到,因為無法訪問或修改源代碼。而且要想修改原始的類,需要理解代碼中的同步策略,這樣增加的功能才能與原有的設計保持一致。
- 2. 另一種方法是擴展這個類,假設這個類在設計時考慮了可擴展性。
/*** 擴展Vector很簡單,但并非所有的類都像Vector那樣將狀態向子類公開,因此也就不適合采用這種方法。* 并且擴展比直接將代碼添加到類中更脆弱,如果底層的類改變了同步策略并選擇了不同的鎖來保護它的狀態變量,那么子類會被破壞,* 因為同步策略改變后它無法再使用正確的鎖來控制對基類狀態的并發訪問(Vector在規范中定義了它的同步策略,因此不存在問題)。* @param <E>*/
public class ExtendVector<E>
extends Vector<E>
{public synchronized boolean putIfAbsent(E x){boolean absent = !
contains(x);if(absent){add(x);}return absent;}
} - 3. 第三種策略是擴展類的功能,但并不是擴展類本身,而是將代碼放入一個‘輔助類’中,在客戶端加鎖。
錯誤的加鎖方式:
// list并不是使用的Demo上的鎖來保護它的狀態,Demo只是帶來了同步的假象,并不能保證putIfAbsent的原子操作
public class Demo<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在實現客戶端加鎖或外部加鎖時使用同一個鎖。
/*** 客戶端加鎖是指,對于使用某個對象X的客戶端代碼,使用X本身用于保護其狀態的鎖來保護這段客戶端代碼。* 因此要使用客戶端加鎖,你必須知道對象X使用的是哪一個鎖。*/
public class Demo<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;}}
} 通過添加一個原子操作來擴展類是脆弱的,因為它將類的加鎖代碼分布到多個類中。然而,客戶端加鎖卻更加脆弱,因為它類C的加鎖代碼放到與C完全無關的其他類中。在那些并不承諾遵循加鎖策略的類上使用客戶端加鎖需要格外小心。客戶端加鎖與擴展類機制有許多共同點,都將派生類的行為與基類的失效耦合在一起,這破壞了同步策略的封裝性。
/*** ExtendList通過將List對象的操作委托給底層的List實例來實現,同時添加一個原子的putIfAbsent方法。* 其實這里同樣使用了Java監視器模式來封裝現有的list,ExtendList通過自身的內置鎖增加了一層額外的鎖。* 不需要再關心底層List是否線程安全,即使List不是線程安全或修改了加鎖的實現,ExtendList也能提供一致的加鎖機制來實現線程安全性。* 雖然額外的同步層可能導致輕微的性能損失,但與模仿另一個對象的加鎖策略相比,則更為健壯。* @param <E>*/
public class ExtendList<E>
implements List<E>
{private final List<E>
list;public ExtendList(List<E>
list){this.list =
list;}public synchronized boolean putIfAbsent(E x){boolean contains =
list.contains(x);if(contains){list.add(x);}return !
contains;}//類似方式委托list的其他方法
}
?
#筆記內容參考 《 java并發編程實戰》
轉載于:https://www.cnblogs.com/shanhm1991/p/9899016.html
總結
以上是生活随笔為你收集整理的java多线程3.设计线程安全类的全部內容,希望文章能夠幫你解決所遇到的問題。
如果覺得生活随笔網站內容還不錯,歡迎將生活随笔推薦給好友。