Il y a essentiellement deux questions que vous lui posez:
1. Peut l' getInstance()
de retour de méthode null
en raison de la réorganisation?
(je pense que c'est ce que vous êtes vraiment après, donc je vais essayer d'y répondre en premier)
Même si je pense que la conception de Java pour permettre pour ce qui est totalement fou, il semble que vous êtes tout à fait correct qu' getInstance()
peut retourner la valeur null.
Votre exemple de code:
if (resource == null)
resource = new Resource(); // unsafe publication
return resource;
est logiquement 100% identique à l'exemple dans le billet de blog est lié à:
if (hash == 0) {
// calculate local variable h to be non-zero
hash = h;
}
return hash;
Jeremy Manson, puis il décrit que son code peut retourner 0 en raison de la réorganisation. Au début, je ne l'ai pas cru que je pensais que le suivant "passe-avant"-logique doit tenir:
"if (resource == null)" happens before "resource = new Resource();"
and
"resource = new Resource();" happens before "return resource;"
therefore
"if (resource == null)" happens before "return resource;", preventing null
Mais Jeremy donne l'exemple suivant dans un commentaire sur son blog, comment ce code peut être valablement réécrit par le compilateur:
read = resource;
if (resource==null)
read = resource = new Resource();
return read;
Ce, dans un seul thread de l'environnement, se comporte exactement identique à l'original du code, mais, dans un environnement multi-thread peut conduire à la suite de l'exécution de la commande:
Thread 1 Thread 2
------------------------------- -------------------------------------------------
read = resource; // null
read = resource; // null
if (resource==null) // true
read = resource = new Resource(); // non-null
return read; // non-null
if (resource==null) // FALSE!!!
return read; // NULL!!!
Maintenant, à partir d'une optimisation du point de vue, cela ne fait aucun sens pour moi, depuis le point de l'ensemble de ces choses serait de réduire les multiples lectures au même endroit, auquel cas il ne fait aucun sens que le compilateur ne génère pas d' if (read==null)
au lieu de cela, à la prévention du problème. Donc, comme Jeremy points dans son blog, il est probablement très peu probable que cela arrive un jour. Mais il semble que, purement à partir d'une langue-règles de point de vue, il est en fait autorisé.
Cet exemple est en fait couverte dans le JLS:
http://docs.oracle.com/javase/specs/jls/se7/html/jls-17.html#jls-17.4
L'effet observé entre les valeurs de r2
, r4
, et r5
en Table 17.4. Surprising results caused by forward substitution
est équivalent à ce qui peut se produire avec l' read = resource
, l' if (resource==null)
, et l' return resource
dans l'exemple ci-dessus.
Aparté: Pourquoi dois-je faire référence le blog en tant que source ultime pour la réponse? Parce que le mec qui l'a écrit, est aussi le gars qui a écrit le chapitre 17 de l'JLS sur la simultanéité! Donc, il vaut mieux être de droite! :)
2. Seraient Resource
immuable faire l' getInstance()
méthode thread-safe?
Étant donné le potentiel null
de résultat, ce qui peut se produire indépendamment du fait que d' Resource
est mutable ou pas, la réponse simple à cette question est: Non (non strictement)
Si nous ignorons ce très peu probable, mais possible scénario, cependant, la réponse est: Dépend.
L'évidence thread problème avec le code, c'est qu'il peut conduire à la suite de l'exécution de l'ordre (sans le besoin pour n'importe quel réorganisation):
Thread 1 Thread 2
---------------------------------------- ----------------------------------------
if (resource==null) // true;
if (resource==null) // true
resource=new Resource(); // object 1
return resource; // object 1
resource=new Resource(); // object 2
return resource; // object 2
Ainsi, la non-thread-safety est venue du fait que vous pourriez obtenir deux objets différents de retour de la fonction (même si sans réorganisation aucun d'eux ne sera jamais null
).
Maintenant, ce que le livre était probablement en train de dire est la suivante:
La Java des objets immuables comme les Chaînes et les nombres Entiers essayez d'éviter de créer plusieurs objets pour le même contenu. Donc, si vous avez "hello"
en un seul endroit et "hello"
dans un autre endroit, Java vous donnera exactement la même référence d'objet. De même, si vous avez new Integer(5)
en un seul endroit et new Integer(5)
dans un autre. Si c'était le cas avec new Resource()
ainsi, vous obtenez les mêmes références et object 1
et object 2
dans l'exemple ci-dessus serait exactement le même objet. Cela conduirait en effet à un "thread-safe" de la fonction (en ignorant la réorganisation de problème).
Mais, si vous implémentez Resource
- toi, je ne crois pas qu'il existe même une façon pour le constructeur de renvoyer une référence à un objet créé précédemment plutôt que d'en créer un nouveau. Donc, il ne devrait pas être possible pour vous de faire object 1
et object 2
être exactement le même objet. Mais, étant donné que vous êtes d'appeler le constructeur avec les mêmes arguments (aucun dans les deux cas), il pourrait être probable que, même si vos objets créés ne sont pas exactement le même objet, ils seront, à toutes fins et intentions, se comportent comme si elles l'étaient, aussi efficacement du code thread-safe.
Cela ne doit pas nécessairement être le cas. Imaginez un immuable version de Date
, par exemple. Le constructeur par défaut Date()
utilise le système actuel, le temps que la date de valeur. Donc, même si l'objet est immuable et le constructeur est appelé avec le même argument, l'appelant deux fois ne sera probablement pas le résultat dans un objet équivalent. Par conséquent, l' getInstance()
méthode n'est pas thread-safe.
Donc, comme une déclaration générale, je crois que la ligne que vous avez cité le livre est tout simplement faux (à moins que sorti de son contexte ici).
OUTRE Re: réorganisation
Je trouve l' resource==new Resource()
exemple un peu trop simpliste pour m'aider à comprendre POURQUOI le fait de permettre une telle réorganisation par Java ferais jamais de sens. Alors permettez-moi de voir si je peux venir avec quelque chose, si cela peut effectivement aider à l'optimisation:
System.out.println("Found contact:");
System.out.println(firstname + " " + lastname);
if (firstname==null) firstname = "";
if (lastname ==null) lastname = "";
return firstname + " " + lastname;
Ici, dans la plupart des cas probable que les deux ifs
rendement false
, il n'est pas optimale à faire cher la concaténation de Chaîne firstname + " " + lastname
deux fois, une fois pour le message de débogage, une fois pour le retour. Donc, il serait en effet de bon sens ici pour réorganiser le code pour faire ce qui suit:
System.out.println("Found contact:");
String contact = firstname + " " + lastname;
System.out.println(contact);
if ((firstname==null) || (lastname==null)) {
if (firstname==null) firstname = "";
if (lastname ==null) lastname = "";
contact = firstname + " " + lastname;
}
return contact;
À titre d'exemples deviennent plus complexes et que vous commencez à penser à le compilateur de garder la trace de ce qui est déjà chargé/calculé dans le processeur registres qu'il utilise intelligemment et sauter de re-calcul des résultats, cet effet pourrait en fait devenir plus et plus susceptibles de se produire. Donc, même si je n'ai jamais pensé que je pourrais jamais dire que quand je suis au lit la nuit dernière, en y réfléchissant plus, je ne fait maintenant croire que cela peut avoir été un besoin/une bonne décision pour vraiment permettre l'optimisation du code pour faire sa partie la plus impressionnante de la magie. Mais il ne me paraît assez dangereux car je ne pense pas que beaucoup de gens sont conscients de cela et même s'ils le sont, c'est assez complexe pour envelopper votre tête autour de la façon d'écrire votre code correctement sans la synchronisation de tout (qui sera alors de faire disparaître plusieurs fois avec toute la performance, avantages plus flexible de l'optimisation).
Je suppose que si vous n'avez pas permettre cette réorganisation, toute la mise en cache et la réutilisation des résultats intermédiaires d'une série d'étapes de processus deviendrait illégal, s'éloignant l'un des plus puissants les optimisations du compilateur possible.