曾宪杰的《大型网站系统与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之后,原书中说存在并发陷阱???
Il n’est pas verrouillé.
MODIFIER :
Il existe effectivement des pièges à concurrence. Considérez cette situation :
Le thread A exécute map.get(key);if(value == null) pour obtenir le résultat vrai, puis transmet le temps CPU.
À ce moment-là, le thread B s'exécute également au même endroit, et le résultat est également vrai, car le thread A n'a pas encore exécuté map.put(key, 1) et le thread B a exécuté map.put (key, 1) , À ce moment, la carte a déjà la valeur de key.
Le thread A obtient du temps CPU pour continuer l'exécution. Étant donné que le résultat du jugement précédent était vrai, le thread A est remis en place. Le résultat final est que les deux threads exécutent map.put(key, 1) une fois. À ce stade, la valeur de key est toujours 1, mais elle devrait en réalité être 2.
La raison pour laquelle ce problème existe est qu'une seule opération de ConcurrentHashMap est atomique, mais votre appel externe n'est pas atomique. map.get et map.put sont deux opérations indépendantes l'une de l'autre, donc si vous voulez vous assurer du thread. Pour des raisons de sécurité, vous devez toujours ajouter des verrous à votre code pour garantir l'atomicité des opérations get et put.
ConcurrentHashMap garantit uniquement que ses données internes peuvent rester cohérentes dans des conditions concurrentes, ce que HashMap ne peut pas faire.
Mais la méthode add n'est pas thread-safe car il s'agit d'un Check-Then-Act ou Read-Modify-Write typique.
Vous pouvez réfléchir à cette question, si tous les champs d'une classe sont des classes thread-safe, alors si cette classe est thread-safe. La réponse est évidemment non.