Java併發編程實戰第四章對象的組合

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));
}

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章