java - 下面这段代码存在并发陷阱???
PHP中文网
PHP中文网 2017-04-18 10:10:31
0
2
544

曾宪杰的《大型网站系统与Java中间件实践》第一章第1.2.2.3小节给出以下代码示例:

使用HashMap数据被进行统计;

public class TestClass
{
    private HashMap<String, Integer> map = new HashMap<>();
    
    public synchronized void add(String key)
    {
        Integer value = map.get(key);
        if(value == null)
        {
            map.put(key, 1);
        }
        else
        {
            map.put(key, value + 1);
        }
    } 
}

使用ConcurrentHashMap保存数据并进行统计;

public class TestClass
{
    private ConcurrentHashMap<String, Integer> map = new ConcurrentHashMap<>();
    
    public void add(String key)
    {
        Integer value = map.get(key);
        if(value == null)
        {
            map.put(key, 1);
        }
        else
        {
            map.put(key, value + 1);
        }
    } 
}

使用HashMap时,对add方法加锁,此时该方法是线程安全的,为何换为ConcurrentHashMap之后,原书中说存在并发陷阱???

PHP中文网
PHP中文网

认证高级PHP讲师

全部回覆(2)
巴扎黑

為何換成ConcurrentHashMap之後,還要加鎖add方法? ? ?

public void add(String key)
    {
        Integer value = map.get(key);
        if(value == null)
        {
            map.put(key, 1);
        }
        else
        {
            map.put(key, value + 1);
        }
    } 

沒加鎖啊。

EDIT:

確實是存在並發陷阱。考慮一下這種情況:

  1. 執行緒A執行map.get(key);if(value == null)得到結果true, 然後交出cpu時間。

  2. 此時, 執行緒B也執行到同一個地方, 得到結果也為true, 因為執行緒A還沒執行map.put(key, 1), 執行緒B執行map.put(key, 1),此時map中已經有key的值了。

  3. 線程A得到CPU時間繼續執行, 因為之前判斷結果是true, 所以線程A又put了一次。最後的結果就是兩個執行緒都執行了一次map.put(key, 1), 此時key的值依舊為1, 但實際上應該是2。

存在這個問題是原因是, ConcurrentHashMap的單一操作是原子性的, 但是你外部呼叫並不是原子性的, map.get 和map.put 是兩個操作, 相互獨立的操作, 所以你如果要保證線程安全依舊需要在你的程式碼上加鎖, 保證get和put兩個操作的原子性。

左手右手慢动作

ConcurrentHashMap只保證在並發情況下其內部資料能夠保持一致,而這一點HashMap是做不到的。

但是add方法並不是線程安全,因為這是典型的Check-Then-Act或Read-Modify-Write。

你可以思考這樣一個問題,如果一個類別裡的所有field都是線程安全的類,那麼這個類是否線程安全。答案顯然是否定的。

熱門教學
更多>
最新下載
更多>
網站特效
網站源碼
網站素材
前端模板