2017-10-12 115 views
2

正如Java_author提到的,客戶端鎖定是否違反同步策略的封裝?

客戶端鎖定需要守着使用一些對象X與鎖定的客戶端代碼,X使用來保護它自己的狀態。


在下面的代碼即對象X是list。以上說的是,使用鎖所擁有的ListHelper類型的對象來同步putIfAbsent(),是一個錯誤的鎖。

package compositeobjects; 

import java.util.ArrayList; 
import java.util.Collections; 
import java.util.List; 

public class ListHelper<E> { 

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

但是,Java的作者說,

客戶端鎖定有很多共同的類擴展,他們倆夫婦的派生類的基礎實施的行爲類。正如擴展違反實現封裝[EJ條款14]一樣,客戶端鎖定違反了對同步策略的封裝

我的理解是,Collections.synchronizedList()返回的嵌套類實例,也使用了list所擁有的鎖對象。


爲什麼ListHelper客戶端鎖定(與list)的使用,違反了同步策略的封裝?

+2

因爲'synchronizedList'使用自己作爲監視器,而目前情況恰好如此。如果此實現更改,您的代碼將無法正常工作。 –

+0

@AndyTurner明白了。通過'ArrayList'監控模式是個好主意。你建議嗎? – overexchange

+0

您知道您的實用程序類可以被['synchronizedSet'](https://docs.oracle.com/javase/8/docs/api/java/util/Collections.html#synchronizedSet(java.util。組))? –

回答

2

您正在依靠synchronizedList將自己用作顯示器這一事實,目前情況恰好如此。

你甚至要依賴於該synchronizedList使用​​實現同步,這也恰好是目前真正的事實(這是一個合理的假設,但它不是一個是必要的)。

有些方法可能會改變synchronizedList的實現,導致您的代碼無法正常工作。


例如,the constructor of synchronizedList

SynchronizedList(List<E> list) { 
    super(list); 
    // ... 
} 

可改爲

SynchronizedList(List<E> list) { 
    super(list, new Object()); 
    // ... 
} 

現在,在SynchronizedList執行方法使用的mutex場不再this(有效) ,因此list上的外部同步將不再起作用。


隨着中說,在使用synchronized (list)確實有預期的效果在Javadoc描述,所以這種行爲不會改變的事實,所以你現在正在做的是精絕;它的設計使用了漏洞抽象,所以如果你從頭開始做類似的事情,就不應該這樣設計,但是漏洞抽象的屬性被記錄下來。

+0

爲什麼'如果實施改變'代碼無法正常工作? imho'synchronized(list)'是必須的,否則'Set'-wise操作將不起作用。 –

+0

@ M.leRutte想想如果[這行代碼]會發生什麼(http://grepcode.com/file/repository.grepcode.com/java/root/jdk/openjdk/8u40-b25/java/util/ Collections.java#2399)改爲'super(list,new Object())'(或['this line'](http://grepcode.com/file/repository.grepcode.com/java/root/jdk/) openjdk/8u40-b25/java/util/Collections.java#2006)改爲'this.mutex = new Object()')。互斥體不再是列表本身,所以在'list'上同步將不起作用。 –

+0

啊,是的,如果列表將使用不同於'this'的監視器,並且另一個線程將訪問列表,並且示例代碼將被執行,那麼它將出錯。我懂了。無論如何,看到我的答案,這段代碼已經很容易出錯,因爲它需要所有代碼才能通過實用程序類。 –

0

你的代碼基本上創建了一個同步集。它只添加了元素,如果它不在列表中,那麼就是set的定義。

無論同步列表如何執行自己的鎖定,您必須提供自己的鎖定機制,因爲有兩個對同步列表的調用,其中列表將釋放其鎖定。因此,如果兩個線程添加相同的對象,他們都可以通過contains檢查並將其添加到列表中。化合物synchronize確保它不是。最重要的是,所有的列表使用代碼都會經過您的實用程序類,否則它將會失敗。

正如我在評論中所寫的,使用synchronized set可以實現完全相同的行爲,這也將確保在鎖定整個操作時尚未添加該元素。通過使用這個同步的設置訪問和修改而不使用你的工具類是可以的。


編輯:

如果你的代碼需要一個列表,而不是一組,和LinkedHashSet是不是我會創建一個新的同步列表我自己的選擇:你是靠

public class SynchronizedList<E> implements List<E> { 
    private List<E> wrapped = new ArrayList<E>(); 

    .... 
    @override 
    public int size() { 
     synchronized(this) { 
      return wrapped.size(); 
     } 
    } 

    .... 
    @override 
    public void add(E element) { 
    synchronized(this) { 
     boolean absent = !wrapped.contains(x); 
     if(absent) { 
      wrapped.add(element); 
     } 
     return absent; 
    } 
} 
+0

啊,我需要基於位置的元素集合,但也需要'putIfAbsent'。這是一個正確的用法嗎? – overexchange

+0

是否會包裝['LinkedHashSet'](https://docs.oracle.com/javase/7/docs/api/java/util/LinkedHashSet.html)?保持順序,但它沒有'get(index)',這是我個人很少需要的。 –

+0

上面的使用案例是關於*爲現有的線程安全類*添加功能,因此,* putIfAbsent *是今天爲現有的「ListHelper 」引入的。 – overexchange