Thread-safe cache d'un objet en java
disons que nous avons un CountryList objet dans notre application, qui doit renvoyer la liste des pays. Le chargement de pays est une opération lourde, de sorte que la liste doit être mis en cache.
Exigences supplémentaires:
- CountryList doit être thread-safe
- CountryList charge de paresseux (sur demande)
- CountryList devrait soutenir l'invalidation du cache
- CountryList doit être optimisé considérant que le cache sera invalidé très rarement
Je suis venu avec la solution suivante:
public class CountryList {
private static final Object ONE = new Integer(1);
//MapMaker is from Google Collections Library
private Map<Object, List<String>> cache = new MapMaker()
.initialCapacity(1)
.makeComputingMap(
new Function<Object, List<String>>() {
@Override
public List<String> apply(Object from) {
return loadCountryList();
}
});
private List<String> loadCountryList() {
//HEAVY OPERATION TO LOAD DATA
}
public List<String> list() {
return cache.get(ONE);
}
public void invalidateCache() {
cache.remove(ONE);
}
}
Qu'en pensez-vous? Voyez-vous quelque chose de mauvais à ce sujet? Est-il d'autres façon de le faire? Comment puis-je faire mieux? Dois-je regarder tout à fait une autre solution dans ce cas?
Grâce.
- Je ne suis pas convaincu que c'est thread-safe. Que faire si deux threads appel invalidateCache() simultanément ou l'une est la liste d'appel() en même temps, un autre des appels invalidateCache()?
- Cartographe retourne un "thread-safe" implementationi de l'interface de la Carte. makeComputingMap() effectue le calcul automatiquement (google-collections.googlecode.com/svn/trunk/javadoc/com/google/...)
- voulez-vous accepter une réponse?
- Davis, j'ai accepté ma propre réponse.
Integer.valueOf(1)
obtient une version mise en cache de la clé.
Vous devez vous connecter pour publier un commentaire.
google collections effectivement fournitures juste la chose pour ce genre de chose: Fournisseur
Votre code pourrait être quelque chose comme:
volatile
.get()
besoin d'unreturn
.Merci à vous tous les gars, en particulier pour l'utilisateur "gid" qui a donné l'idée.
Mon objectif était d'optimiser les performances pour le get() de l'opération compte tenu de la invalidate() l'opération sera appelé très rare.
J'ai écrit un test de classe qui commence à 16 threads, chaque appel de get()-Opération d'un million de fois. Avec cette classe, je profilé certains de la mise en œuvre sur mes 2 core maschine.
Les résultats des tests
1) "Pas de synchronisation" n'est pas thread-safe, mais nous donne les meilleures performances que l'on peut comparer à.
2) "Normal synchronisation" - assez bonne performance, la norme no-brainer mise en œuvre
3) "avec Cartographe" - très mauvaise performance.
Voir ma question dans la partie supérieure pour le code.
4) "avec les Fournisseurs.memoize" - de bonnes performances. Mais comme le rendement de la même "Normal synchronisation" nous avons besoin de l'optimiser ou tout simplement utiliser la fonction "Normale synchronisation".
Voir la réponse de l'utilisateur "gid" pour le code.
5) "avec l'optimisation de la memoize" - le performnce comparable à "no sync"-mise en œuvre, mais thread-safe one. C'est l'un nous devons.
Le cache de la classe elle-même:
(Le Fournisseur interfaces utilisée ici est à partir de Google Collections de la Bibliothèque et il n'a qu'une seule méthode get(). voir http://google-collections.googlecode.com/svn/trunk/javadoc/com/google/common/base/Supplier.html)
Exemple d'utilisation:
Chaque fois que j'ai besoin de cache quelque chose, j'aime utiliser le Modèle de Proxy.
De le faire avec ce modèle propose la séparation des préoccupations. L'original de votre
l'objet peut être concerné par le chargement paresseux. Votre proxy (ou le tuteur) de l'objet
peut être responsable de la validation de la cache.
En détail:
par le biais de son interface.
À partir d'ici, vous pouvez insérer votre invalidation du cache de la stratégie dans l'objet proxy. Enregistrez le temps de la dernière charge, et la prochaine fois demande à voir les données, de les comparer à l'heure actuelle pour le cache de temps. Définir un niveau de tolérance, où, si trop de temps s'est écoulé, les données sont rechargées.
Autant que le Lazy Load, reportez-vous ici.
Maintenant, pour certains, de bonne maison, un exemple de code:
Je ne suis pas sûr de ce que la carte est pour. Quand j'ai besoin d'un paresseux, objet mis en cache, j'ai l'habitude de faire comme ceci:
Je pense que c'est similaire à ce que vous êtes en train de faire, mais un peu plus simple. Si vous avez besoin d'une carte et de CELUI que vous avez simplifié à l'écart de la question, ok.
Si vous le souhaitez thread-safe, vous devez synchroniser les obtenir et de les oublier.
Bleah - vous à l'aide d'une structure de données complexe, Cartographe, avec plusieurs fonctionnalités (carte d'accès, la concurrence un accès différé à la construction de valeurs, etc) en raison d'une seule fonction, vous êtes après (reporté à la création d'une construction-cher objet).
Tout en réutilisant le code est un bon objectif, cette approche ajoute une charge supplémentaire et de la complexité. En outre, il trompe les futurs responsables quand ils voient une carte de données structure de là à penser qu'il y a une carte de clés/valeurs dans là quand il ya vraiment seulement 1 chose (liste des pays). La simplicité, la lisibilité et la clarté sont la clé de maintenance.
Semble que vous êtes après chargement différé. Chercher des solutions à d'autres AFIN de chargement différé des questions. Par exemple, celui-ci couvre la classique double-vérifier approche (assurez-vous que vous utilisez Java 1.5 ou version ultérieure):
Comment résoudre le "Double-Vérifier le Verrouillage est Cassé" Déclaration en Java?
Plutôt que de simplement répéter la solution de code ici, je pense qu'il est utile de lire la discussion sur le chargement différé par l'intermédiaire de double-vérifier qu'il n'y à développer votre base de connaissances. (désolé si cela se présente comme une pompeux - essaye juste apprendre à pêcher plutôt que de nourrir des bla bla bla ...)
Il y a une bibliothèque là-bas (à partir de atlassian) - l'un des util classes de LazyReference. LazyReference est une référence à un objet qui peut être paresseusement créé (sur la première à obtenir). il est garantie thread-safe, et l'init est également garantie pour que surviennent une fois - si deux threads appels get() en même temps, un thread de calcul, de l'autre thread va bloquer attendre.
voir un exemple de code:
Vos besoins sembler assez simple ici. L'utilisation de Cartographe fait de la mise en œuvre plus compliqué que ce qu'il a à être. L'ensemble de la double-vérifier le verrouillage de l'idiome est difficile d'obtenir un droit, et ne fonctionne que sur 1.5+. Et pour être honnête, c'est la rupture de l'une des règles les plus importantes de la programmation:
Le double-vérifier le verrouillage de l'idiome essaie d'éviter le coût de la synchronisation dans le cas où le cache est déjà chargé. Mais est-ce que les frais généraux vraiment à l'origine des problèmes? Vaut-il le coût de code plus complexe? Je dis supposer qu'il n'est pas jusqu'à ce que le profilage vous dit le contraire.
Voici une solution très simple qui ne nécessite pas de 3ème partie du code (en ignorant les JCIP annotation). Elle permet de faire l'hypothèse que la liste vide signifie que le cache n'a pas encore été chargé. Il empêche également le contenu de la liste des pays d'échapper à code client qui pourrait potentiellement modifier la liste retournée. Si ce n'est pas une préoccupation pour vous, vous pouvez supprimer l'appel à des Collections.unmodifiedList().
Cela semble ok pour moi (je suppose Cartographe est à partir de google collections?) Idéalement vous n'avez pas besoin d'utiliser une Carte parce que vous n'avez pas vraiment les touches, mais que la mise en œuvre est caché de tout les appelants je ne vois pas cela comme une grosse affaire.
C'est simple d'utiliser le ComputingMap choses. Vous avez seulement besoin d'un simple morts de mise en œuvre où toutes les méthodes sont synchronisés, et vous devriez être bien. Ce sera bien évidemment le bloc le premier thread frapper à elle (avoir), et toute autre thread frapper alors que le premier thread charge le cache (et la même chose si quelqu'un appelle le invalidateCache chose - lorsque vous devez également décider si le invalidateCache devrait charger le cache de nouveau, ou tout simplement nulle sortir, laissant la première tentative d'obtenir de nouveau bloc), mais alors tous les fils doivent traverser joliment.
Utiliser le Initialisation à la demande du titulaire de l'idiome
Suivi de Mike solution ci-dessus. Mon commentaire n'a pas de format comme prévu... 🙁
Regarder dehors pour des problèmes de synchronisation dans operationB, surtout depuis load() est lente:
Vous pu le résoudre de cette façon:
Assurez-vous de TOUJOURS synchroniser sur tous les accès pour le chargement variable.