首頁  >  問答  >  主體

java - 下面这段代码存在并发陷阱???

曾宪杰的《大型网站系统与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中文网2765 天前523

全部回覆(2)我來回復

  • 巴扎黑

    巴扎黑2017-04-18 10:12:31

    為何換成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兩個操作的原子性。

    回覆
    0
  • ringa_lee

    ringa_lee2017-04-18 10:12:31

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

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

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

    回覆
    0
  • 取消回覆