5

我遇到了一個Java類的例子,它聲稱是線程安全的。任何人都可以請解釋它如何可以線程安全?我可以清楚地看到,類中​​的最後一個方法沒有防止任何讀者線程的併發訪問。或者,我在這裏錯過了什麼?部分線程安全是否使Java類線程安全?

public class Account { 
    private Lock lock = new ReentrantLock(); 
    private int value = 0; 
    public void increment() { 
     lock.lock(); 
     value++; 
     lock.unlock(); 
    } 
    public void decrement() { 
     lock.lock(); 
     value--; 
     lock.unlock(); 
    } 
    public int getValue() { 
     return value; 
    } 
} 
+0

將volatile關鍵字添加到count將使其線程安全。 – sturcotte06

+0

另外,你的鎖必須是最終的。 – sturcotte06

+0

@ sturcotte06 - 是的......但兩個評論都是重點。這不是關於如何正確編寫代碼。這是關於這個版本的具體屬性,以及它是否「線程安全」......以及這實際上意味着什麼。 –

回答

1

簡短的回答:

根據定義,Account線程安全類即使geValue方法不把守

長的答案

實踐中的Java併發一類被認爲是線程安全的時候:

未設置操作的順序執行或同時在一個線程安全類的 情況下可能會導致一個實例是在 無效狀態。

由於該getValue方法不會導致Account類在任何給定的時間處於無效狀態,你的類被認爲是線程安全的。

Collections#synchronizedCollection文檔共鳴這種情緒:

返回由 指定collection支持的同步(線程安全的)集合。爲了保證串行訪問,通過返回的集合完成對後備集合的所有訪問都是 的關鍵是 。至關重要的是,用戶 迭代 當在返回的集合手動同步它:

Collection c = Collections.synchronizedCollection(myCollection); 
... 
    synchronized (c) { 
    Iterator i = c.iterator(); // Must be in the synchronized block 
    while (i.hasNext()) 
    foo(i.next()); 
    } 

注意的文檔怎麼說,收集(這是在Collections名爲SynchronizedCollection內部類的一個對象類)是線程安全的,但仍然要求客戶端代碼在對它進行迭代時保護集合。事實上,SynchronizedCollection中的iterator方法不是​​。這與您的示例非常相似,其中Account是線程安全的,但在調用getValue時,客戶端代碼仍然需要確保原子性。

+0

與您提供的示例不同,Account類的調用方無法獲得「有效」值,因爲鎖定是內部的。 – jtahlborn

+0

@jtahlborn我所做的一點是JDK具有需要外部鎖定的線程安全類。需要外部鎖定的類仍然可以稱爲線程安全的。我有沒有想念你的觀點? – CKing

+0

jdk類是線程安全的。呼叫者需要做額外的工作才能獲得「一致的」結果。 Account類是「線程安全」的,你不能把它搞砸。然而,調用者可以做到「一致」的結果,所以我認爲這不是一個好的平行結果。 – jtahlborn

0

它完全是線程安全的。

沒有人可以同時遞增和遞減value因此,您不會失去或獲得錯誤計數。

事實上,getValue()通過時間返回不同的值是無論如何將發生的事情:同時性是不相關的。

+1

該操作是原子操作,但始終可以運行內存可見性問題。所以我不會說它完全是線程安全的。請參閱Stephen C的解答。 –

+0

我不這麼認爲。由於您不知道處理器將如何處理這些線程,因此我明確指出,併發性並不相關。 – Bathsheba

0

您不必保護getValue。同時從多個線程訪問它不會導致任何負面影響。無論何時或從多少線程調用此方法(因爲它不會更改),對象狀態不會變得無效。

話雖如此 - 你可以寫一個使用這個類的非線程安全的代碼。

例如像

if (acc.getValue()>0) acc.decrement(); 

是潛在的危險,因爲它可能會導致競爭條件。爲什麼?假設你有一個業務規則「永遠不會低於0」,你的當前值是1,並且有兩個線程正在執行這個代碼。有可能他們會按以下順序執行:

  1. 線程1檢查acc.getValue是否> 0。是!

  2. 線程2,acc.getValue> 0。是!

  3. 線程1調用遞減。值爲0

  4. 線程2調用遞減。值爲-1

發生了什麼事?每個功能都確保它不會低於零,但他們一起設法做到了這一點。這叫做競賽條件。

爲了避免這種情況,您必須保護基本操作,而不是保護必須不間斷執行的任何代碼。

所以,這個類是線程安全的,但僅限於非常有限的用途。

+0

查看更新的答案。 –

+0

CKing的回答也涵蓋了這一點。 在一個線程安全類的實例上沒有按順序或同時執行一組操作會導致一個實例處於無效狀態。 所以,如果任何值是有效的,那麼代碼是線程安全的。但是,如果你對「有效狀態」是什麼有更多的概念(比如它是> 0,或者與不同的對象相關等),那麼代碼就不是線程安全的。 –

+0

yu_sha,難道我們不能說班級不是線程安全的嗎?因爲它會導致競爭狀態嗎?請滾動並查看我的示例下面的加入帳戶。 – softwarelover

6

該代碼不是線程安全的。

假設一個線程調用decrement,然後第二個線程調用getValue。怎麼了?

問題是沒有「發生在和getValue之間的關係」之間。這意味着沒有保證,getValue調用將看到decrement的結果。實際上,getValue可能會「錯過」不確定的incrementdecrement調用序列的結果。

實際上,除非我們看到使用Account類的代碼,否則線程安全性的問題是不明確的。程序的線程安全性的傳統概念是關於代碼是否正確地行爲,而不管線程相關的非確定性如何。在這種情況下,我們沒有規定什麼是「正確的」行爲,或者確實是一個可執行程序來測試或檢查。

但我的代碼的解讀是,有一個隱含的API要求/正確性標準是getValue返回賬戶的當前。如果存在多個線程,則無法保證,因此該類不是線程安全的。

相關鏈接:


1 - 在@該死的回答實踐報價併發還呼籲的 「正確性」 這個概念被提「無效狀態「在定義中。但是,內存模型上的JLS部分不指定線程安全性。相反,他們談論「形式良好的執行」。

2 - 此閱讀材料由OP的評論支持。然而,如果你不接受這個要求是真實的(例如,因爲它沒有明確說明),那麼另一方面是「賬戶」抽象的行爲取決於Account類別以外的代碼......哪些使其成爲「泄漏抽象」。

+0

正是!這就是我想看到某人寫的東西。我們可以想象一個聯合賬戶被兩個合作伙伴訪問,並根據他們在兩個不同的ATM屏幕(可能位於兩個不同的城市)上看到的數量(getValue)做出決定(撤回或存款?)!合作伙伴可能會看到10美元,但是當他的線程想要提取5美元時,另一個合作伙伴的線程可能已經提取了10美元,導致帳戶餘額爲 - 5美元!目前對班級的定義存在風險! – softwarelover

+0

作爲一個例子,爲什麼StringBuffer的設計者在Java標準API中調用該類是線程安全的,而不知道應用程序開發人員將如何使用它(線程安全方式與非線程安全方式)? – softwarelover

+0

@softwarelover該類被認爲是線程安全的。請參閱我的回答,以獲取JDK類的示例,該JDK類被稱爲線程安全的,但仍需要客戶端代碼來確保線程安全。它引起了你的質疑。也就是說,在這個答案中指出,在其他類中的任何類的使用可能不是線程安全的。 – CKing

2

這不是線程安全的,純粹是因爲沒有關於編譯器如何重新排序的保證。由於價值是不揮發這裏是你的經典例子:

while(account.getValue() != 0){ 

} 

這可以提升至樣子

while(true){ 
    if(account.getValue() != 0){ 

    } else { 
     break; 
    } 
} 

我能想象有編譯器樂趣其他排列這可能會導致這個微妙地失敗。但通過多線程訪問getValue可能會導致失敗。

2

這裏有幾個明顯的問題:

問:如果多個線程做出increment()decrement()重疊的電話,然後他們停下來,然後足夠的時間傳球沒有線程調用increment()decrement(),會的getValue ()返回正確的數字? A:是的。在增量和減量方法中的鎖定確保了每個增量和減量操作將以原子方式發生。他們不能互相干擾。


問:足夠的時間是多久?

答:很難說。 Java語言規範並不保證調用getValue()的線程有史以來看到由其他線程寫入的最新值,因爲getValue()根本不訪問任何同步地訪問該值。

如果更改getValue()來鎖定和解鎖同一鎖定對象,或者如果聲明計數爲volatile,則零時間量就足夠了。


問:到getValue()調用可以返回無效價值?答:不可以,它只能返回初始值,或者完成調用的結果或完成的結果decrement()操作。

但是,這個原因有與鎖無關。鎖確實是而不是阻止任何線程調用getValue(),而其他一些線程正在增加或減少值。

防止getValue()返回完全無效值的事情是該值爲int,並且JLS保證更新和讀取的int變量總是原子的。