http://codinghx.com/2016/04/18/%E5%B9%B6%E5%8F%91%E7%BC%96%E7%A8%8B%E5%9B%9B%E4%B9%8B%E5%AF%B9%E8%B1%A1%E7%9A%84%E7%BB%84%E5%90%88/
1、設計線程安全類的3個基本要素:
- 找出構成對象狀態的所有變量。
- 找出約束狀態變量的不變性條件。
- 建立對象狀態的併發訪問管理策略。
要滿足狀態變量的有效值或狀態轉換上的各種約束條件,就需要藉助於原子性與封裝性。
2、實例封閉
將數據封裝在對象內部,可以將數據的訪問限制在對象的方法上,從而更容易確保線程在訪問數據時總能持有正確的鎖。
封閉機制更容易構造線程安全的類,因爲當封閉類的狀態時,在分析類的線程安全性時就不需檢查整個程序。
Java監視器模式:遵循Java監視器模式的對象會把對象的所有可變狀態都封裝起來,並由對象自己的內置鎖來保護。
3、線程安全性的委託
委託給單個線程安全的狀態變量;委託給多個彼此獨立的狀態變量;
4、在現有的線程安全類中添加功能
修改原始的類;擴展現有的類;客戶端加鎖(注意要使用正確的鎖!)
實例封閉
如果某對象不是線程安全的,那麼可以通過多種技術使其在多線程程序中安全地使用。我們可以通過一個鎖來保護對該對象的所有訪問,或者確保該對象只能由單個線程訪問(線程封閉)
封裝簡化了線程安全類的實現過程,它提供了一種實例封閉機制。當一個對象被封裝到另一個對象中時,能夠訪問被封裝對象的所有代碼路徑都是已知的。通過將封閉機制與合適的加鎖策略結合起來,可以確保以線程安全的方式來使用非線程安全的對象。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
|
@ThreadSafe public
class
PersonSet { @GuardedBy ( "this" ) private
final
Set<Person> mySet = new
HashSet<Person>(); public
synchronized
void
addPerson(Person p) { mySet.add(p); } public
synchronized
boolean
containsPerson(Person p) { return
mySet.contains(p); } interface
Person { } } |
上述程序中的HashSet並非線程安全,但因爲mySet私有並且不會逸出,被封閉在PersonSet中。唯一能訪問mySet的代碼路徑時addPerson和containsPerson,在執行它們時都要活的PersonSet上的鎖。PersonSet的狀態完全由它的內置鎖保護,因而PersonSet是一個線程安全的類。
上面程序中還需要注意的一點是,如果Person類是可變的,那麼在訪問從PersonSet中獲得的Person對象時,還需要額外的同步,我們可以使Person成爲一個線程安全的類,也可以用鎖來保護Person對象。
Java監視器模式
1
2
3
4
5
6
7
8
9
10
11
12
13
14
|
@ThreadSafe public
final
class
Counter { @GuardedBy ( "this" ) private
long
value = 0 ; public
synchronized
long
getValue() { return
value; } public
synchronized
long
increment() { if
(value == Long.MAX_VALUE) throw
new
IllegalStateException( "counter
overflow" ); return
++value; } } |
Java監視器模式僅僅是一種編寫代碼的約定,遵循Java監視器模式的對象會把對象的所有可變狀態都封裝起來,並由對象自己的內置鎖來保護。上面的程序就是這種模式的一個簡單的典型例子。
監視器模式示例:車輛追蹤
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
|
@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); } } |
1
2
3
4
5
6
7
8
9
10
11
12
13
14
|
@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; } } |
雖然類MutablePoint不是線程安全的,但追蹤器類是線程安全的。它所包含的Map對象和可變的Point對象都未曾發佈。通過MutablePoint拷貝構造函數或者deepCopy方法來複制正確的值,從而生成一個新的Map對象,其中的鍵值對與原來的相同。因爲是新的Map對象,MutablePoint也是新的對象,這樣避免了獲取locations的人對原來的MutablePoint進行修改。
注意:車輛容量非常大的情況將極大地降低性能,因爲deepCopy是從一個synchronized中調用的,因此需要執行較長時間的複製操作時,內置鎖會一直被佔有,當有大量車輛需要追蹤時,會嚴重降低用戶界面的響應靈敏度。此外,由於是複製數據到新的對象,那麼當車輛的實際位置發生變化時,返回的信息會保持不變。這種情況是好是壞,取決用戶的需求。
線程安全性的委託
示例:基於委託的車輛追蹤器
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
|
@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); } } |
1
2
3
4
5
6
7
8
9
|
@Immutable public
class
Point { public
final
int
x, y; public
Point( int
x, int
y) { this .x
= x; this .y
= y; } } |
Point類是不可變的,因而是線程安全的,可以被自由地共享和發佈。
DelegatingVehicleTracker中沒有使用任何顯示的同步,所有對狀態的訪問都由ConcurrentHashMap來管理。
如果使用最初的MutablePoint類而不是Point類,就會破壞封裝性,因爲getLocations會發佈一個指向可變狀態的引用,而這個引用不是線程安全的。
注意:與之前監聽器模式不同,這裏返回的是一個不可修改但卻實時的車倆位置視圖。
如果需要一個不發生變化的車輛視圖,那麼getLocations可以返回對locations這個Map對象的一個拷貝。如下程序所示
1
2
3
|
public
Map<String, Point> getLocations() { return
Collections.unmodifiableMap( new
HashMap<String, Point>(locations)); } |