1. 程式人生 > >並發編程3

並發編程3

每次 con 還需要 客戶端 bool 必須 函數 並不會 bsp

1、實例封閉

class personset{
    private final Set<Person> myset = new HashSet<Person>();
    
    public void addPersom(Person p){
        myset.add(p);
    }
    
    public boolean containPerson(Person p){
        return myset.contains(p);
    }
}

  這個類的狀態是由HashSet來進行管理的,這裏的myset是私有的且並不會逸出,因此HashSer被封閉在personset中,所以如果不對myset進行訪問那這個類就是線程安全的,但是由於HashSet並不是線程安全的,所以其add和contains方法都不是線程安全的,所以需要加上鎖。

class personset{
    private final Set<Person> myset = new HashSet<Person>();

    public synchronized void addPersom(Person p){
        myset.add(p);
    }

    public synchronized boolean containPerson(Person p){
        return myset.contains(p);
    }
}

  這樣personset的狀態完全由它的內置鎖保護著,因而它就是一個線程安全的類。

  這個例子中並未對Person的線程安全性進行假設,如果Person類是可變的,那麽從personset中獲得Person對象時還需要額外的同步

  示例:車輛追蹤

class MutablePoint{
    private int x;
    private int y;
    public MutablePoint(){
        this.x = 0;
        this.y = 0;
    }
    public MutablePoint(MutablePoint m){
        this.x = m.getX();
        this.y = m.getY();
    }
    
public synchronized int getX() { return x; } public synchronized void setX(int x) { this.x = x; } public synchronized int getY() { return y; } public synchronized void setY(int y) { this.y = y; } }  

  MutablePoint是一個記錄車輛坐標的類  

public class MonitorVehicleTracker {
    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);
    }

    private 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)));
        }
        //返回的是一個不可修改的Map
        return Collections.unmodifiableMap(result);
    }
}

  MonitorVehicleTracker是一個線程安全的追蹤器,它所包含的Map對象和可變的MutablePoint都是對外未曾發布的當需要返回車輛的位置的時候,通過MutablePoint的構造函數來復制新的值。

2、線程安全性的委托

  還可以創建一個不可變的Point類來替換MutablePoint,然後構造一個委托給線程安全的車輛追蹤器,我們可以先把數據存儲在一個線程安全的ConcurrentHashMap類中。

class Point{
    public final int x, y;
    public Point(int x,int y){
        this.x = x;
        this.y = y;
    }
}

  這個Point是不可變的,因此它是線程安全,因為不可變的值可以自由的分享和發布,所以在返回location的時候不需要再復制。

public class MonitorVehicleTracker {
    private final ConcurrentHashMapConcurrentHashMap<String,Point> locations;
    private final Map<String, Point> unmodifiableMap;
public MonitorVehicleTracker(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 unmodifiableMap.get(id); }
}

  這裏沒有使用明顯的同步,所有對狀態的訪問都由ConcurrentHashMap來管理,創建對象的構造器中先講數據存儲在ConcurrentHashMap類型的對象中,再調用Collections的unmodifibleMap方法將數據傳給unmodifiableMap,這個方法返回指定映射的不可修改視圖以保證線程安全,這樣想要修改unmodifiableMap中的數據(想要修改這個對象的狀態),只能通過構造器新建一個對象了。

3、當委托失效時

class NumberRange{
    private final  AtomicInteger lower = new AtomicInteger(0);
    private final  AtomicInteger upper = new AtomicInteger(0);
    public void setLower(int i){
     //這裏是不安全的
if(i > upper.get()){ throw new IllegalArgumentException( "can‘t set lower to " + i + " > upper "); } lower.set(i); } public void setUpper(int i){
     //這裏是不安全的
if(i < lower.get()){ throw new IllegalArgumentException( "can‘t set lower to " + i + " > upper "); } upper.set(i); } public boolean isInrange(int i ){ return (i >= lower.get() && i <= upper.get()); } }

  這裏的NumberRange並不是線程安全的,setLower和setUpper都是先檢查後執行的操作。這裏會出現的問題就是如果線程A想要修改了upper為4,但是並沒有進行到set這一行,線程B想要修改lower為5是可以通過檢查的。結果可能會變成(5,4)。

  結論就是如果某個類含有復合操作,就像上面這樣,僅靠委托並不足以實現線程安全性,在這種情況下,這個類必須提供自己的加鎖機制保證這些復合操作都是原子操作,除非整個復合操作都可以委托給狀態變量。

4、 客戶端加鎖機制

class ListHelps<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。這裏的鎖僅僅只是鎖住了“如果沒有則添加”這個操作,所以如果線程A進行了這個操作,其他線程並不能執行“如果沒有則添加”這個操作。但是如果線程A在“其他線程調用另外的方法添加了x”之前已經執行完驗證x的存在。總的來說就是putIfAbsent相對於其他list的操作並不是原子性的。

class ListHelps<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;
        }
    }
}

  上面的代碼給出了如何使putIfAbsent相對於其他list的操作變得是原子性的。

5、組合

class ImprovedList<T> implements List<T>{
    private List<T> list;

    public  ImprovedList(List<T> list){
        this.list = list;
    }
    public synchronized  boolean putIfAbsent(T x){
        boolean absent = !list.contains(x);
        if (absent)
            list.add(x);
        return absent;
    }
}

  這樣的組合將存儲數據的list,也就是真正代表類的狀態的list變量封閉在了ImprovedList中,然後putIfAbsent是一個原子性的操作,這樣某線程在執行putIfAbsent的時候其他線程也無法修改list的值。

6、總結

  第一個例子實例封閉,將代表對象狀態的變量 myset定義為final類型的,然後把使用這個變量的方法都加上鎖,這樣就能保證多線程程序使用這個類的時候的原子性。

  第二個記錄車輛坐標的類中,記錄對象狀態的變量的初始化是通過復制得到的,且得到的是一個不可修改的map,每次返回數據的時候是通過新建一個對象返回的,實現了把map變量(代表對象狀態的變量)封閉在了對象中。而且在初始化和返回數據操作時加上了內置鎖,實現了線程安全

  第三個也是記錄車輛坐標的,這裏使用了線程安全的ConcurrentHashMap,真正代表對象狀態的map變量獲取數據也是從這個ConcurrentHashMap中獲得,而從外界獲取數據則是ConcurrentHashMap,從而實現了線程安全。

  第四個例子中代表對象狀態的兩個變量使用AtomicInteger,這裏只實現了數據讀寫的線程安全,但是在邏輯上數據的使用,也就是先檢查後執行的操作是根據數據的內容進行的,而且這裏的setLower和setUpper兩個操作的結果還是互相影響的,所以如果想要加鎖,也需要把這兩個操作加在一塊。

  第五個和第六個想要解決的問題是一樣的,第五個把關於list的操作加上鎖實現線程安全,第六個把list封閉在對象中,實現每次進行putIfAbsent操作的時候其他線程無法改變list的內容

並發編程3