Est ConcurrentHashMap.get() de la garantie de voir un précédent ConcurrentHashMap.put() par thread différent?
Est ConcurrentHashMap.get()
garanti de voir un précédent ConcurrentHashMap.put()
par thread différent? Mon espoir est que est, et la lecture de la documentation Javadoc semble l'indiquer, mais je suis à 99% convaincu que la réalité est différente. Sur mon serveur de production ci-dessous semble se produire. (Je l'ai attrapé avec l'exploitation forestière.)
Pseudo-code exemple:
static final ConcurrentHashMap map = new ConcurrentHashMap();
//sharedLock is key specific. One map, many keys. There is a 1:1
// relationship between key and Foo instance.
void doSomething(Semaphore sharedLock) {
boolean haveLock = sharedLock.tryAcquire(3000, MILLISECONDS);
if (haveLock) {
log("Have lock: " + threadId);
Foo foo = map.get("key");
log("foo=" + foo);
if (foo == null) {
log("New foo time! " + threadId);
foo = new Foo(); //foo is expensive to instance
map.put("key", foo);
} else
log("Found foo:" + threadId);
log("foo=" + foo);
sharedLock.release();
} else
log("No lock acquired");
}
Ce qui semble se passer, c'est ceci:
Thread 1 Thread 2
- request lock - request lock
- have lock - blocked waiting for lock
- get from map, nothing there
- create new foo
- place new foo in map
- logs foo.toString()
- release lock
- exit method - have lock
- get from map, NOTHING THERE!!! (Why not?)
- create new foo
- place new foo in map
- logs foo.toString()
- release lock
- exit method
Donc, ma sortie ressemble à ceci:
Have lock: 1
foo=null
New foo time! 1
foo=foo@cafebabe420
Have lock: 2
foo=null
New foo time! 2
foo=foo@boof00boo
Le deuxième thread ne pas voir immédiatement le mettre! Pourquoi? Sur mon système de production, il y a plus de fils et j'ai seulement vu un thread, le premier qui suit immédiatement le thread 1, ont un problème.
J'ai même essayé de réduire le niveau de simultanéité sur ConcurrentHashMap à 1, non pas qu'il devrait avoir de l'importance. E. g.:
static ConcurrentHashMap map = new ConcurrentHashMap(32, 1);
Où vais-je tort? Mon attente? Ou est-il un bug dans mon code (le vrai logiciel, pas le ci-dessus) qui en est la cause? J'ai dépassé à plusieurs reprises et je suis sûr à 99% que je suis de la manipulation du verrouillage correctement. Je ne peux même pas imaginer un bug dans ConcurrentHashMap
ou de la JVM. Merci de me sauver de moi-même.
Gorey détails qui pourraient être utiles:
- quad-core 64-bit Xeon (DL380 G5)
- RHEL4 (
Linux mysvr 2.6.9-78.0.5.ELsmp #1 SMP
...x86_64 GNU/Linux
) - Java 6 (
build 1.6.0_07-b06
,64-Bit Server VM (build 10.0-b23, mixed mode)
)
C'est le point où je deviens paranoïaque. En outre, l'enregistrement de la dernière foo droit avant de relâcher le verrou, également vous connecter à la carte.get( "clé" ), et l'identité de code de hachage pour la carte. Sur rapide lu, il est certainement tout a l'air ok et en fait, le verrouillage doit être suffisant si la carte n'est pas modifié par ailleurs. Peut-être il ya quelque chose d'important que de ne pas le faire à travers le dépouillement de la question? <haussement d'épaules>
Un proxy (ou simplement étendre) ConcurrentHashMap et du journal le calendrier et les arguments pour get() et(), ce qui pourrait faire la lumière
Oh, et quand vous écrivez quelque chose, il suffit d'utiliser le Système.err.println (): à noter que c'est le Système.err, pas de Système.; ce dernier est mis en mémoire tampon, l'ancien ne l'est pas.
cela n'a rien à voir avec votre problème, mais je vous conseille fortement de vous mettre la
sharedLock.release()
appeler à l'intérieur d'un finally
bloc. Si une exception se produit entre le tryAcquire
et la release
appels, le sémaphore sera à la fin fermé pour toujours.OriginalL'auteur Stu Thompson | 2009-11-20
Vous devez vous connecter pour publier un commentaire.
Quelques bonnes réponses ici, mais autant que je sache, personne n'a effectivement fourni une réponse canonique à la question posée: "Est ConcurrentHashMap.get() de la garantie de voir un précédent ConcurrentHashMap.put() par thread différent". Ceux qui ont dit oui n'ont pas fourni une source.
Donc: oui, c'est garanti. Source (voir la section "Mémoire de la Cohérence Propriétés"):
OriginalL'auteur Cowan
Cette question de la création d'un coûteux à créer un objet dans un cache fondée sur l'incapacité à trouver dans le cache est connu de problème. Et heureusement, cela avait déjà été mis en œuvre.
Vous pouvez utiliser Cartographe de Google Collecitons. Vous donnez simplement un rappel qui crée l'objet, et si le code client recherche dans la carte et la carte est vide, la fonction de rappel est appelée et le résultat mettre dans la carte.
Voir Cartographe javadoc ...
BTW, dans votre exemple il n'y a aucun avantage à utiliser un ConcurrentHashMap, comme vous le verrouillage de chaque accès, pourquoi ne pas simplement utiliser une normale HashMap à l'intérieur de votre verrouillé section?
Ok, je vois. Cartographe doit encore travailler pour vous.
Pourriez-vous élaborer sur le "défaut de trouver dans le cache est un problème connu", dans son exemple, il a les etats, lorsqu'il accède à la cache c'est nul. J'aimerais en savoir plus sur ce scénario, ou me diriger dans une direction pour en savoir plus.
Il y a une tendance générale où vous en avez besoin pour calculer une valeur sur la base de certains intrants, mais le calcul est coûteux, de sorte que vous voulez ré-utiliser précédemment valeurs calculées quand vous le pouvez. Le contept est appelé memoization (voir en.wikipedia.org/wiki/Memoization). Google MapMaker (voir lien ci-dessus dans mon commentaire) est un moyen simple de mise en œuvre de memoziation en java avec des cartes.
Si j'utilise une table de hachage et de ne pas ConcurrentHashMap puis les autres threads ne sont pas garantis pour voir les changements. Ma Carte de changement de valeur au fil du temps. Hélas, c'est muet maintenant...j'ai opté pour un dramatique refactoring (frais de réécriture) et tout à l'air bon.
OriginalL'auteur David Roussel
Une chose à considérer est de savoir si vos clés sont égaux et ont les mêmes hashcodes à deux reprises de "get" appel. Si ils sont juste
String
s alors oui, il ne va pas être un problème ici. Mais comme vous n'avez pas donné le type générique de la clés, et vous avez ramené "sans importance" détails dans le pseudo-code, je me demande si vous utilisez une autre classe comme une clé.Dans tous les cas, vous pouvez en outre journal le hashcode de clés utilisées pour les gets/les met dans les fils 1 et 2. Si ces derniers sont différents, vous avez votre problème. Notez également que
key1.equals(key2)
doit être vrai; ce n'est pas quelque chose que vous pouvez vous connecter de façon définitive, mais si les touches ne sont pas définitive des classes, il serait utile de consignation de leur nom de classe entièrement qualifié, puis, regardant la méthode equals() pour la classe ou les classes pour voir si il est possible que la deuxième clé pourrait être considérée comme inégale à la première.Et pour répondre à votre titre - oui, ConcurrentHashMap.get() est garanti de voir sur la précédente put(), où les "anciens", il y a une se passe-avant relation entre les deux, comme spécifié par le Modèle Mémoire Java. (Pour le ConcurrentHashMap en particulier, c'est essentiellement ce que vous attendez, avec la mise en garde que vous ne pouvez pas être en mesure de dire qui se passe d'abord si les deux threads s'exécutent à "exactement de la même temps" sur les différents cœurs. Dans votre cas, cependant, vous devriez certainement voir le résultat de la mettre() dans le thread 2).
OriginalL'auteur Andrzej Doyle
Si un thread met une valeur dans simultanées de hachage de la carte, puis un autre thread qui récupère la valeur de la carte est la garantie de voir les valeurs insérées par le thread précédent.
Cette question a été clarifiée dans "la Java de la Simultanéité dans la Pratique" par Joshua Bloch.
Citant le texte :-
OriginalL'auteur Ankit Kumar
Je ne pense pas que le problème est dans le "ConcurrentHashMap", mais plutôt quelque part dans votre code ou sur le raisonnement à propos de votre code. Je ne peux pas place à l'erreur dans le code ci-dessus (peut-être que nous n'avons tout simplement pas voir le mauvais?).
Mais pour répondre à votre question "Est ConcurrentHashMap.get() de la garantie de voir un précédent ConcurrentHashMap.put() par thread différent?" J'ai bidouillé un petit programme de test.
En bref: Non, ConcurrentHashMap est OK!
Si la carte est mal écrit le programme suivant shoukd impression de "Mauvais accès à!" au moins de temps en temps. Il jette à 100 Threads avec 100000 appels à la méthode décrite ci-dessus. Mais il imprime "Tout est ok!".
Ma machine dispose de deux cœurs.
Les tests multi-threaded code comme celui-ci ne fonctionne pas. Toutes les itérations sont susceptibles de courir avec exactement le même fil, des entrelacements.
Bon point. Je devrais avoir connu ce.
ok ... comment voulez-vous tester?
OriginalL'auteur Arne Deutsch
Mise à jour:
putIfAbsent()
est logiquement correct ici, mais n'évite pas le problème de la création de Foo dans le cas où la clé n'est pas présente. Il crée toujours le Foo, même si elle n'arrive pas à le mettre dans la carte. David Roussel réponse est bonne, en supposant que vous pouvez accepter les Google Collections de la dépendance dans votre application.Peut-être que je suis absent quelque chose d'évident, mais pourquoi êtes-vous en gardant la carte avec un Sémaphore?
ConcurrentHashMap
(CHM) est thread-safe (en supposant que c'est en toute sécurité publié, c'est ici). Si vous essayez d'obtenir atomique "sur place, si ce n'est pas déjà là", utilisez chm.putIfAbsent()
. Si vous avez besoin de plus complciated invariants où le contenu de la carte ne peut pas changer, vous devrez probablement utiliser un HashMap et de le synchroniser comme d'habitude.Pour répondre à votre question plus directe: une Fois votre mettre retourne, la valeur que vous avez mis la carte est garantie d'être vu par le prochain thread qui le cherche.
Note de côté, juste un +1 à certains autres commentaires à propos de mettre le sémaphore de presse dans une enfin.
.putIfAbsent()
ne fonctionne pas pour moi-à la création de l'objet est cher.Pas sûr que j'ai suivi. Dans l'exemple ci-dessus, les touches de la carte sont juste (immuable) les Chaînes de caractères. Même si elles ne sont pas dans le code réel, vous êtes juste à l'aide de la clé à ressembler à quelque chose (à droite?), donc je n'ai pas immédiatement voir pourquoi la clé elle-même a besoin d'un garde.
Il y a une relation 1:1 entre la touche et le cher Foo instance. Je ne veux pas créer plus d'un Foo instance simultanément. J' vous voulez de nouvelles Foo instances à créer pour les autres touches, en même temps, si besoin est. Je ne veux pas sérialiser tous les Foo création de l'instance pour une clé.
Ok, je vois ce que vous dites. Je, soussigné, comme David Roussel répondre alors 🙂 (j'ai travaillé dans Scala trop, a été pensée putIfAbsent() n'exécuter le " new Foo()' si la clé est absente, mais c'est évidemment pas le cas)
Scala ne serait pas exemple Foo dans ce contexte? Wow...j'ai vraiment, vraiment besoin de regarder la Scala...
OriginalL'auteur overthink
Assistons-nous à une intéressante manifestation de la Java du Modèle de Mémoire? Dans quelles conditions sont les registres vidé la mémoire principale? Je pense que c'est garanti que si deux threads synchroniser sur le même objet, puis ils vont voir un uniforme de la mémoire de vue.
Je ne sais pas ce Semphore n'en interne, il doit évidemment faire quelques synchroniser, mais savons-nous?
Ce qui se passe si vous ne
au lieu de l'acquisition de la sémaphore?
Foo
est si cher à créer (secondes!) que je suis 99.9999% sûr que c'est OK, je vois que le thread 2 ne pas entrer dans la logique jusqu'à ce que après le thread 1 a publié le verrou. EtSemaphore
est, après tout, dansjava.util.concurrent
...il doit être vraiment bon.Je vais essayer votre suggestion bien que ce soir. Un des avantages clés de Sémaphore, c'est qu'il arrive à expiration et je peux quitter. Ce qui se passe dans une webapp demande, de verrouillage et d'un fil de façon permanente peut prendre vers le bas le serveur. Mauvais. Baaaaaad.
La question est de savoir si ces "CPU" les registres pour le thread 1 sont vidées, les seules garanties que je peux trouver sont synchronisées est utilisé. Je suis d'accord qu'il serait surprenant si c'était le problème, mais qu'avons-nous d'autre? Vous pouvez utiliser wait/notify à la mise en œuvre d'un délai d'attente.
Je vais garder cela à l'esprit. Mais je pense que pour maintenant, je vais poursuivre le "compteur CHM sous-classe" journalisation avec
System.err
proposé ailleurs. Pourrait tout aussi bien faire la même chose pour le Sémaphore pendant que j'y suis...Même sans une sorte de synchronisation de bloc, le contenu réel d'un ConcurrentHashMap est stocké dans le champ déclaré que les volatiles, de sorte que les lectures à partir d'un thread sont toujours garanti (ou au moins censée être garantie) pour voir les changements effectués à partir d'un autre thread.
OriginalL'auteur djna
Pourquoi êtes-vous de verrouillage en même temps une carte de hachage? Par def. son thread-safe.
Si il y a un problème, de ses dans votre code de verrouillage.
C'est pourquoi nous avons thread-safe packages en Java
La meilleure façon de déboguer c'est avec une barrière de synchronisation.
OriginalL'auteur bigtalktheory