3 回答

TA貢獻(xiàn)1863條經(jīng)驗(yàn) 獲得超2個(gè)贊
我想知道僅在方法上同步是否線程安全
add
?contains
如果不同步,是否有任何可能的問題?
簡短回答:否和是。
有兩種解釋方式:
直觀的解釋
Java 同步(以其各種形式)防止許多事情,包括:
兩個(gè)線程同時(shí)更新共享狀態(tài)。
一個(gè)線程試圖讀取狀態(tài),而另一個(gè)正在更新它。
線程看到過時(shí)的值,因?yàn)閮?nèi)存緩存尚未寫入主內(nèi)存。
在您的示例中,同步add
就足以確保兩個(gè)線程不能同時(shí)更新HashSet
,并且兩個(gè)調(diào)用都將在最新HashSet
狀態(tài)下運(yùn)行。
但是,如果contains
也不同步,則contains
調(diào)用可能會與調(diào)用同時(shí)發(fā)生add
。這可能導(dǎo)致contains
調(diào)用看到 的中間狀態(tài)HashSet
,從而導(dǎo)致不正確的結(jié)果,或者更糟。如果調(diào)用不是同時(shí)發(fā)生的,這也會發(fā)生,因?yàn)楦臎]有立即刷新到主內(nèi)存和/或讀取線程沒有從主內(nèi)存讀取。
內(nèi)存模型解釋
JLS 指定了 Java 內(nèi)存模型,它規(guī)定了多線程應(yīng)用程序必須滿足的條件,以保證一個(gè)線程可以看到另一個(gè)線程所做的內(nèi)存更新。該模型是用數(shù)學(xué)語言表達(dá)的,并不容易理解,但要點(diǎn)是當(dāng)且僅當(dāng)從寫入到后續(xù)讀取之間存在一系列 happen before關(guān)系時(shí),才能保證可見性。如果寫入和讀取在不同的線程中,那么線程之間的同步是這些關(guān)系的主要來源。例如在
// thread one
synchronized (sharedLock) {
sharedVariable = 42;
}
// thread two
synchronized (sharedLock) {
other = sharedVariable;
}
假設(shè)線程一的代碼在線程二的代碼之前運(yùn)行,則線程一釋放鎖和線程二獲取鎖之間存在happens before關(guān)系。有了這個(gè)和“程序順序”的關(guān)系,我們就可以建立一個(gè)從寫入42到賦值到的鏈條other。這足以保證other將被分配42(或可能是變量的以后值)并且sharedVariable之前沒有任何值42被寫入它。
如果synchronized塊不在同一個(gè)鎖上同步,第二個(gè)線程可能會看到一個(gè)過時(shí)的值sharedVariable;即之前寫入的一些值42被分配給它。

TA貢獻(xiàn)1810條經(jīng)驗(yàn) 獲得超5個(gè)贊
該代碼對于該 synchronized (pSet) { }部分是線程安全的:
if (!pSet.contains(x)) {
synchronized (pSet) {
// Here you are sure to have the updated value of pSet
if (!pSet.contains(x)) {
// do some exclusive work with x.
pSet.add(x);
}
}
因?yàn)樵趕ynchronized對象的聲明中pSet:
一個(gè)且只有一個(gè)線程可能在這個(gè)塊中。
在其中,pSet它的更新狀態(tài)也由與 synchronized 關(guān)鍵字的 happens-before 關(guān)系保證。
因此,無論等待線程的第一個(gè)語句返回的值是什么if (!pSet.contains(x)),當(dāng)這個(gè)被等待的線程醒來并進(jìn)入語句時(shí) synchronized,它都會設(shè)置最后更新的值pSet。因此,即使前一個(gè)線程添加了相同的元素,第二個(gè)線程 if (!pSet.contains(x))也會返回false。
但是這段代碼對于if (!pSet.contains(x))在寫入Set.
根據(jù)經(jīng)驗(yàn),不應(yīng)該使用未設(shè)計(jì)為線程安全的集合來執(zhí)行并發(fā)的寫入和讀取操作,因?yàn)榧系膬?nèi)部狀態(tài)可能處于正在進(jìn)行/不一致的狀態(tài),以進(jìn)行同時(shí)發(fā)生的讀取操作一個(gè)寫操作。
雖然一些非線程安全的集合實(shí)現(xiàn)在事實(shí)中接受了這樣的用法,但這根本不能保證它總是正確的。
所以你應(yīng)該使用線程安全的Set實(shí)現(xiàn)來保證整個(gè)線程安全。
例如:
Set<String> pSet = ConcurrentHashMap.newKeySet();
這在引擎蓋下使用 a ConcurrentHashMap,因此沒有讀取鎖和最小的寫入鎖(僅在要修改的條目上而不是整個(gè)結(jié)構(gòu)上)。

TA貢獻(xiàn)1982條經(jīng)驗(yàn) 獲得超2個(gè)贊
不,
您不知道在另一個(gè)線程添加期間哈希集可能處于什么狀態(tài)。可能正在進(jìn)行根本性的更改,例如存儲桶的拆分,因此在另一個(gè)線程添加期間包含可能會返回false ,即使該元素將存在于單線程 HashSet 中。在那種情況下,您將嘗試第二次添加元素。
更糟糕的情況:由于兩個(gè)線程同時(shí)使用的內(nèi)存中的 HashSet 處于臨時(shí)無效狀態(tài),contains可能會陷入死循環(huán)或拋出異常。
添加回答
舉報(bào)