並發編程3
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