曾宪杰的《大型网站系统与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之后,原书中说存在并发陷阱???
巴扎黑2017-04-18 10:12:31
으아아아ConcurrentHashMap으로 변경한 후 add 메소드를 잠가야 하는 이유는 무엇인가요? ? ?
잠겨있지 않습니다.
실제로 동시성 함정이 있습니다. 다음 상황을 고려해보세요.
스레드 A는 map.get(key);if(value == null)을 실행하여 결과를 true로 얻은 다음 CPU 시간을 넘겨줍니다.
이때 스레드 B도 같은 위치에 실행되며 결과도 true입니다. 스레드 A는 아직 map.put(key, 1)을 실행하지 않았고 스레드 B는 map.put을 실행했기 때문입니다. (key, 1) , 이때 맵에는 이미 key 값이 있습니다.
스레드 A는 실행을 계속하기 위해 CPU 시간을 얻습니다. 이전 판단 결과가 true였기 때문에 스레드 A가 다시 실행됩니다. 최종 결과는 두 스레드가 모두 map.put(key, 1)을 한 번 실행한다는 것입니다. 이때 key의 값은 여전히 1이지만 실제로는 2여야 합니다.
이 문제가 발생하는 이유는 ConcurrentHashMap의 단일 작업이 원자적이지만 외부 호출은 원자적이지 않기 때문입니다. map.get과 map.put은 서로 독립적인 두 작업이므로 스레드를 보장하려면 안전을 위해서는 get 및 put 작업의 원자성을 보장하기 위해 코드에 잠금을 추가해야 합니다.
ringa_lee2017-04-18 10:12:31
ConcurrentHashMap은 HashMap이 할 수 없는 동시 조건 하에서 내부 데이터가 일관성을 유지할 수 있다는 것만 보장합니다.
그러나 add 메소드는 일반적인 Check-Then-Act 또는 Read-Modify-Write이므로 스레드로부터 안전하지 않습니다.
클래스의 모든 필드가 스레드로부터 안전한 클래스인 경우 이 클래스가 스레드로부터 안전한지 여부에 대해 이 질문에 대해 생각해 볼 수 있습니다. 대답은 분명히 '아니요'입니다.