private volatile Object obj = new MyObject();
void foo()
{
synchronized(obj)
{
obj.doWork();
}
}
void bar()
{
synchronized(obj)
{
obj.doWork();
obj = new MyObject(); // <<<< notice this line (call it line-x)
}
}
Suppose at a certain point in time, a thread t_bar is executing bar(), and another one t_foo is executing foo, and that t_bar has just acquired obj, so t_foo is, in effect, waiting.After the sync-block in bar is executed, foo will get to execute its sync-block, right? What value of obj would it see? The old one? Or the new one set in bar?(I would hope that the new value is seen, that's the whole point of coding it that way, but I want to know if this is a 'safe' bet)
5 Réponses :
Ceci est dangereux et cassé. Changer l'objet que vous verrouillez ne fonctionne pas. P>
Lorsqu'un thread tente d'entrer un bloc synchronisé, il doit d'abord évaluer l'expression de parens afin de déterminer ce que la serrure a besoin. Si le verrouillage change après cela, le fil n'a aucun moyen de savoir que, il éventuellement acquiert l'ancien verrouillage et entre dans le bloc synchronisé. À ce stade, il voit l'objet et évalue cela, obtenant la nouvelle référence et appelle la méthode sur celle-ci avec l'ancienne verroue (maintenant non pertinente) et sans tenir le nouveau verrou, même si un autre thread pourrait avoir la nouvelle serrure tenue et pourrait exécuter la méthode sur le même objet simultanément. p>
Il se comportera normalement comme si la référence d'objet n'était pas modifiée en interne. La raison étant que le test pour le verrouillage de l'objet ne sera effectué qu'une seule fois. Donc, même si l'objet change en interne, le thread continuera à attendre et le comportement sera motivé comme si l'objet était le même [inchangé].
J'ai essayé une autre chose. J'ai placé une déclaration de sommeil juste après la création d'un nouvel objet, puis a commencé le prochain thread et comme prévu que les deux threads ont commencé à fonctionner simultanément. Voir le code ci-dessous. P>
public class ChangeLockObjectState {
private volatile Object obj = new Object();
void foo() {
synchronized (obj) {
try {
System.out.println("inside foo");
Thread.sleep(10000);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
void bar() {
synchronized (obj) {
try {
System.out.println("inside bar");
Thread.sleep(5000);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
obj = new Object(); // <<<< notice this line (call it line-x)
System.out.println("going out of bar");
try {
Thread.sleep(5000);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
System.out.println("wait over");
}
}
/**
* @param args
* @throws InterruptedException
*/
public static void main(String[] args) throws InterruptedException {
final ChangeLockObjectState test = new ChangeLockObjectState();
new Thread(new Runnable() {
@Override
public void run() {
test.bar();
}
}).start();
Thread.sleep(6000);
new Thread(new Runnable() {
@Override
public void run() {
test.foo();
}
}).start();
}
}
La nouvelle valeur de de la section de la norme sur Arrive avant : p>
Une écriture dans un champ volatil (§8.3.1.4) se produit - avant chaque lecture ultérieure de ce champ. P>
blockQuote>
de la définition de la variable partagée: p>
Tous les champs d'instance, les champs statiques et les éléments de tableau sont stockés dans la mémoire du tas. Dans ce chapitre, nous utilisons la variable de terme pour désigner les champs et les éléments de réseau.
Variables locales (§14.4), paramètres de méthode formels (§8.4.1) et paramètres de gestionnaire d'exception (§14.20) ne sont jamais partagés entre les threads et ne sont pas affectés par le modèle de mémoire. P>
blockQuote>
La lecture de obj code> sera lu. p>
obj code> à l'intérieur du bloc synchronisé est distincte de l'évaluation initiale de l'expression obj code> pour déterminer le moniteur de référence de l'objet à verrouiller. La réaffectation de obj code> se produira avant la première lecture, mais pas la seconde. Etant donné que obj code> est un champ code> volatile code>, cette deuxième lecture doit voir la valeur mise à jour de obj code>. P>
Comment est-il lié à cette question?
Oups, tu as raison. Répondit la mauvaise question :-(
Changé pour répondre à une question correcte.
La nouvelle valeur est affichée. Et cela fonctionne même sans la fabrication obj code> volatile code>. C'est parce que la sychronisation est toujours tenue sur l'ancien objet et fournit une visibilité à la nouvelle valeur une fois que le fil d'attente (T_FOO) arrive à l'intérieur. Voici le test: public class Main3 {
private MyObject obj = new MyObject(1);
void foo()
{
synchronized(obj)
{
System.out.println(obj.number);
obj.doWork();
}
}
void bar()
{
synchronized(obj)
{
System.out.println(obj.number);
obj.doWork();
//force the foo thread to wait at the synchronization point
for(int i = 0; i < 1000000000l; i++);
obj = new MyObject(2); // <<<< notice this line (call it line-x)
}
}
public static void main(String[] args) throws InterruptedException {
final Main3 m3 = new Main3();
Thread t1 = new Thread( new Runnable() {
@Override
public void run() {
m3.bar();
}
});
Thread t2 = new Thread(new Runnable() {
@Override
public void run() {
m3.foo();
}
});
t1.start();
t2.start();
}
}
class MyObject {
int number;
public MyObject(int number) {
this.number = number;
}
public void doWork() {
}
}
Dans la situation exacte que vous avez décrite, oui, la lecture de La partie amusante est, elle ne 'T toujours se passe dans cette situation exacte. Le programme n'est pas le fil de sécurité, par exemple, si immédiatement après Nous pouvons probablement partiellement le réparer par p> obj code> inside le bloc synchronisé de FOO verra la nouvelle valeur définie par le bloc synchronisé de la barre précédente. barre () code>, les mêmes threads invoque une autre barre () code>, tandis que le fil FOO se verrouille sur l'ancien objet. Le fil à barres verrouille sur le nouvel objet, de sorte que les deux threads exécutant simultanément, les deux exécutant obj.dowork () code> sur le même nouvel obj. P> // suppose this line happens-before foo()/bar() calls
MyObject obj = new MyObject();
void foo()
while(true)
MyObject tmp1 = obj;
synchronized(tmp1)
MyObject tmp2 = obj;
if(tmp2==tmp1)
tmp2.doWork();
return;
// else retry
Comment se fait-il que foo code> bloque toujours l'objet code> old code>? Après bar code> sort, obj code> aura une nouvelle valeur partout, oui?
@One Non, FOO tiendra toujours une serrure à l'ancien objet. Voir docs.oracle.com/javase /specs/jls/se7/html/jls-14.html#jls-14 .19 ou regardez la réponse à ma question 2 modifié il y a (où j'ai répondu cette question i> .
OK, il tient la serrure à l'ancien objet que je comprends. Mais il ressemble à n'importe quelle référence à l'intérieur i> le bloc de synchronisation fera référence à la nouvelle valeur ob obj code> ...?
Le fil FOO verrouillé sur l'ancien objet, puis à l'intérieur du bloc, il voit le nouvel objet, alors le travail est fait sur le nouvel objet sous la serrure de l'ancien objet.
@javapirate: Je trouve qu'il est très impoli que vous avez édité mon poste simplement parce que vous préférez votre propre style de mise en forme K & R? Je suis désolé, mais je devrai reformater le reformater.
Pardon. Je pensais avoir de débarrasser beaucoup d'espaces vides. Vas-y!
Une distinction qui pourrait aider à comprendre est que Objets i> ou non final ou non, la variable qui les détiennent des références sont. La serrure est sur l'objet, pas sur la variable.
@MiséraVariable Je comprends cela, mais merci pour la clarification!