摘要: 詳細的解析: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線程獲得的布爾類型。因此這個類並非線程安全的。java
得到變量的鎖就能夠改變變量,沒有得到變量鎖的就不能改變,得到方法的鎖就能夠執行方法裏面的語句。安全