摘要: 詳細的解析:Collections.synchronizedList 1 :關注要點,為什么在有synchroniezed方法的同時會出現 Collections.synchronizedList 2 :知識背景: 您可能需要了解java Synchronized方法的加鎖的各種機制,包括如何上鎖,鎖對象 3 : plus: 您需要不斷的深化 Java加鎖的各種機制 @NotThreadSafe class BadListHelper <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提供一個原子操作:若沒有則添加。因為ArrayList本身不是線程安全的,所以通過集合Collections.synchronizedList將其轉換為一個線程安全的類,然后通過一個輔助的方法來為List實現這么個功能。初看起來這個方法沒問題,因為也添加了synchronized關鍵字實現加鎖了。 但是仔細分析,你會發現問題。首先對於synchronized關鍵字,需要說明的是,它是基於當前的對象來加鎖的,上面的方法也可以這樣寫: public boolean putIfAbsent(E x) { synchronized(this) { boolean absent = !list.contains(x); if (absent) list.add(x); return absent; } } 所以這里的鎖其實是BadListHelper對象, 而可以肯定的是Collections.synchronizedList返回的線程安全的List內部使用的鎖絕對不是BadListHelper的對象,應為你在聲明和初始化這個集合的過程之中,你尚且都不知道這個對象的存在。所以BadListHelper中的putIfAbsent方法和線程安全的List使用的不是同一個鎖,因此上面的這個加了synchronized關鍵字的方法依然不能實現線程安全性。 下面給出書中的另一種正確的實現: @ThreadSafe class GoodListHelper <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; } } } 如果你要分析這個實現是否正確,你需要搞清楚Collections.synchronizedList返回的線程安全的List內部使用的鎖是哪個對象,所以你得看看Collections.synchronizedList這個方法的源碼了。該方法源碼如下:
public static <T> List<T> synchronizedList(List<T> list) { return (list instanceof RandomAccess ? new SynchronizedRandomAccessList<T>(list) : new SynchronizedList<T>(list)); } 通過源碼,我們還需要知道ArrayList是否實現了RandomAccess接口: public class ArrayList<E> extends AbstractList<E> implements List<E>, RandomAccess, Cloneable, java.io.Serializable 查看ArrayList的源碼,可以看到它實現了RandomAccess,所以上面的synchronizedList放回的應該是SynchronizedRandomAccessList的實例。接下來看看SynchronizedRandomAccessList這個類的實現: static class SynchronizedRandomAccessList<E> extends SynchronizedList<E> implements RandomAccess { SynchronizedRandomAccessList(List<E> list) { super(list); } SynchronizedRandomAccessList(List<E> list, Object mutex) { super(list, mutex); } public List<E> subList(int fromIndex, int toIndex) { synchronized(mutex) { return new SynchronizedRandomAccessList<E>( list.subList(fromIndex, toIndex), mutex); } } static final long serialVersionUID = 1530674583602358482L; private Object writeReplace() { return new SynchronizedList<E>(list); } } 因為SynchronizedRandomAccessList這個類繼承自SynchronizedList,而大部分方法都在SynchronizedList中實現了,所以源碼中只包含了很少的方法,但是通過subList方法,我們可以看到這里使用的鎖對象為mutex對象,而mutex是在SynchronizedCollection類中定義的,所以再看看SynchronizedCollection這個類中關於mutex的定義部分源碼: Java代碼 收藏代碼 static class SynchronizedCollection<E> implements Collection<E>, Serializable { private static final long serialVersionUID = 3053995032091335093L; final Collection<E> c; // Backing Collection final Object mutex; // Object on which to synchronize SynchronizedCollection(Collection<E> c) { if (c==null) throw new NullPointerException(); this.c = c; mutex = this; } SynchronizedCollection(Collection<E> c, Object mutex) { this.c = c; this.mutex = mutex; } } 可以看到mutex就是當前的SynchronizedCollection對象,而SynchronizedRandomAccessList繼承自SynchronizedList,SynchronizedList又繼承自SynchronizedCollection,所以SynchronizedRandomAccessList中的mutex也就是SynchronizedRandomAccessList的this對象。所以在GoodListHelper中使用的鎖list對象,和SynchronizedRandomAccessList內部的鎖是一致的,所以它可以實現線程安全性。
class BadListHelper <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; } }
putIfAbsent方法和List並不是使用的同一個鎖對象,List使用的鎖對象並不是BadListHelper,而是list。假如A線程進入putIfAbsent方法,list這個鎖並沒有被獲取(A線程獲取的是 BadListHelper這個對象),所以其他線程還能夠獲得list鎖對象來改變list對象。boolean absent = !list.contains(x);當線程到這串代碼結束時,其他線程獲得list鎖對象,從而就能調用list的方法來改變list對象,這時候就可能導致!list.contains(x)改變,即域absent並不是A線程得到的布爾類型。所以這個類並不是線程安全的。
獲得變量的鎖就可以改變變量,沒有獲得變量鎖的就不能改變,獲得方法的鎖就可以執行方法里面的語句。