2011-07-19 93 views
4

我有一個personId列表。有兩個API調用來更新它(添加和刪除):Java併發 - 使用哪種技術來實現安全?

public void add(String newPersonName) { 
    if (personNameIdMap.get(newPersonName) != null) { 
     myPersonId.add(personNameIdMap.get(newPersonName) 
    } else { 
     // get the id from Twitter and add to the list 
    } 
    // make an API call to Twitter 
} 

public void delete(String personNAme) { 
    if (personNameIdMap.get(newPersonName) != null) { 
     myPersonId.remove(personNameIdMap.get(newPersonName) 
    } else { 
     // wrong person name 
    } 
    // make an API call to Twitter 
} 

我知道可以有併發問題。我讀到3解決方案:

  1. ​​方法
  2. 使用Collections.synchronizedlist()
  3. CopyOnWriteArrayList

我不知道喜歡防止矛盾哪一個。

+0

我建議同步列表,而不是方法。在這種情況下,我肯定會避免使用'CopyOnWriteArrayList',因爲它比突變更適合遍歷。 – Moonbeam

+0

從你的代碼片斷,我不知道你是如何確定你有併發問題?除非先添加項目,否則無法刪除項目,如果項目已在列表中,則無法添加相同的項目。我想我正試圖理解這裏同時發生的事情。 – CoolBeans

+0

如果不同的線程調用上面的'add'和'delete','myPersonId'可以同時添加和刪除。 – Gravity

回答

4

1)同步的方法

2)使用Collections.synchronizedlist

3)的CopyOnWriteArrayList ..

一切都將正常工作,這是什麼樣的性能/功能的問題你需要。

方法#1和#2是阻塞方法。如果你同步這些方法,你自己處理併發。如果您在Collections.synchronizedList中包裝清單,它會爲您處理。 (恕我直言#2是更安全的 - 只是可以肯定的文檔說使用它,不要讓任何訪問被包裹在裏面synchronizedList的原始列表。)

CopyOnWriteArrayList是那些奇怪的事情之一在某些應用中有用。它是一個非阻塞的準不可變列表,即如果線程A在線程B更改它時遍歷列表,則線程A將迭代舊列表的快照。如果你需要非阻塞性能,而且你很少寫信給列表,但經常從中讀取,那麼這也許是最好的選擇。


編輯:還有其他至少兩個選項:

4)使用Vector代替ArrayList; Vector執行List並已同步。然而,它被認爲是一個老派的類(從Java 1.0開始就已經存在了!),並且應該相當於#2。

5)僅從一個線程串行訪問List。如果你這樣做,你保證沒有任何併發​​問題與List本身。一種方法是使用Executors.newSingleThreadExecutor並逐一排隊以訪問列表。這將資源爭用從您的列表移動到ExecutorService;如果任務很短,可能會很好,但如果某些任務冗長,可能會導致其他人阻止比預期更長的時間。最後,您需要考慮應用程序級別的併發性:線程安全性應該是一項要求,並找出如何以最簡單的設計獲得所需的性能。


在附註中,您在add()和delete()中調用personNameIdMap.get(newPersonName)兩次。如果另一個線程修改每個方法中兩個調用之間的personNameIdMap,則會遇到併發問題。你最好做

PersonId id = personNameIdMap.get(newPersonName); 
if (id != null){ 
    myPersonId.add(id); 
} 
else 
{ 
    // something else 
} 
+0

因爲我的操作會少閱讀和寫更多。我想這將是更安全的去與選項2. – harshit

+0

一定要閱讀@ TheLQ的答案,它提出了一些重要的觀點 –

+0

感謝您的更新.. – harshit

3
  1. 同步是使用線程的老辦法。避免使用它來支持主要在java.util.concurrent包中表達的新成語。
  2. 請參閱1.
  3. CopyOnWriteArrayList具有快速讀取和慢速寫入。如果你對它做了很多改變,它可能會開始拖延你的表現。

併發不是關於在單一方法中使用什麼機制或類型的孤立選擇。您需要從更高的層面考慮,以瞭解其所有影響。

+2

我不會註銷'synchronized'作爲工作的「舊方式」使用線程 - 這只是一種阻塞方法,您需要充分了解併發才能使用它,就像您需要充分了解併發性以使用非阻塞數據結構一樣。 java.util.concurrent中唯一的'List <>'(至少在Java 6中,我忘記了如果Java 7增加了什麼)是'CopyOnWriteArrayList',它適用於某些情況。否則,你只剩下一個阻塞方法。 –

+0

'同步'仍然是併發的主要方式,非常普遍。其他所有內容('volatile','java.lang.concurrent.atomic')或多或少都只是在某些特定情況下適用和可用的性能優化。 'CopyOnWriteArrayList'將始終有效,但除非寫入很少,否則不會成功進行性能優化。同樣,它沒有實現任何'synchronized'都無法完成的事情,你只會用它來優化性能。 – Gravity

+0

@Jason:我傾向於認同自己同步事物的想法是不必要的低水平,並且非常難以正確對待。在java.util.concurrent包中轉向由JDK 5和6提供的更高抽象級別會更好。當我們有很多更好更簡單的併發處理方法時,沒有什麼理由在新開發中轉向同步。總的來說,我要強調的一點是做併發權需要一個高級別的橫向視圖,而不是「我應該在這種方法中使用什麼樣的併發性?」做法。 –

4

Collections.synchronizedList是最容易使用,可能是最好的選擇。它只是用​​包裝底層列表。請注意,多步操作(例如for循環)仍然需要由您同步。

一些快速的東西

  • ,除非你真的需要不同步的方法 - 它只是鎖定整個對象,直到方法完成;幾乎沒有理想的效果
  • CopyOnWriteArrayList是一個非常專業化的列表,很可能你不想要,因爲你有一個add方法。它本質上是一個正常的ArrayList,但每次添加東西時,整個陣列都會重建,這是一項非常昂貴的任務。它的線程安全,但不是真正的預期結果
+1

我喜歡這個答案。它提出了兩個重要的點:(1)多步操作需要手動同步;(2)將方法標記爲「同步」(與運行「synchronized(someObj)」塊)來鎖定整個對象並將競爭與任何其他「同步」方法。 –

2

在您發佈的代碼中,所有3種方式都可以接受。但是,有一些特定的特徵:

#3:這應該與#2具有相同的效果,但可能運行得更快或更慢,具體取決於系統和工作負載。

#1:這種方式是最靈活的。只有#1可以使add()和delete()方法更加複雜。例如,如果您需要讀取或寫入列表中的多個項目,則不能使用#2或#3,因爲其他某個線程仍然可以看到該列表已被更新一半。

+0

不,#3不*總是更快。如果你有很多的寫入,它會很慢。請編輯您的答案,否則它將被降級。 –

+0

與Jason同意 – Gravity

2

您是否在這些方法中對personNameIdMap進行了更改,或者是否還要對其中的任何其他數據結構訪問進行同步?如果是這樣,將這些方法標記爲同步可能是最簡單的;否則,您可能會考慮使用Collections.synchronizedList獲取myPersonId的同步視圖,然後通過該同步視圖執行所有列表操作。請注意,在這種情況下,您不應該直接操作myPersonId,而是僅通過從Collections.synchronizedList調用返回的列表進行所有訪問。

無論採用哪種方式,您都必須確保不會出現讀寫和寫入或兩個寫入可能同時發生到同一未同步的數據結構的情況。數據結構記錄爲線程安全的或從Collections.synchronizedList,Collections.synchronizedMap等返回的是這個規則的例外,所以對這些規則的調用可以放在任何地方。非同步數據結構仍然可以在聲明爲同步的方法內安全使用,但是,由於JVM保證這些方法永遠不會同時運行,因此可能沒有並行讀/寫。

0

Java併發(多線程):

併發是運行多個程序或並行程序的若干部分的能力。如果耗時的任務可以異步或並行執行,這可以提高程序的吞吐量和交互性。

我們可以用Java做併發編程。通過Java併發性,我們可以執行並行編程,不變性,線程,執行程序框架(線程池),期貨,可調用函數和fork-join框架編程。