2010-11-17 5 views
1

je suis arrivé cet exercice à la fac:Fils et compteurs partagés en java

Ecrire un programme qui déclare un compteur entier partagé et crée ensuite deux fils, l'un qui tente d'incrémenter le compteur 1000 fois et les autres tentatives de décrémenter le compteur 1000 fois. Lorsque chaque thread a fini de boucler, il devrait imprimer la valeur finale du compteur. (Indice: vous devrez définir une classe de compteurs, pourquoi?) Selon vous, quelle devrait être la sortie? Le programme a-t-il fonctionné comme prévu? Essayez d'exécuter le programme à plusieurs reprises pour voir si vous obtenez toujours le même résultat.

J'ai exécuté le programme en espérant que le résultat final soit zéro, mais il produit en réalité un nombre différent entre 0 et 1000 à chaque fois. Quelqu'un peut-il me dire pourquoi? Merci.

public class Counter 
{ 
    private int val; 

    public Counter() 
    { 
     val = 0; 
    } 

    public void increment() 
    { 
     val = val + 1; 
    } 

    public void decrement() 
    { 
     val = val - 1; 
    } 

    public int getVal() 
    { 
     return val; 
    } 
} 

public class IncThread extends Thread 
{ 
    private static final int MAX = 1000; 
    private Counter myCounter; 

    public IncThread(Counter c) 
    { 
     myCounter = c; 
    } 

    public void run() 
    { 
     for (int i = 0; i < MAX; i++) 
     { 
      myCounter.increment(); 
     } 
    } 

} 

public class DecThread extends Thread 
{ 
    private static final int MAX = 1000; 
    private Counter myCounter; 

    public DecThread(Counter c) 
    { 
     myCounter = c; 
    } 

    public void run() 
    { 
     for (int i = 0; i < MAX; i++) 
     { 
      myCounter.decrement(); 
     } 
    } 
} 

public class Main 
{ 
    public static void main(String[] args) 
    { 
     Counter c = new Counter(); 

     Thread inc = new IncThread(c); 
     Thread dec = new DecThread(c); 

     inc.start(); 
     dec.start(); 

     System.out.println(c.getVal()); 

    } 
} 
+4

pense. C'est le but. – syrion

+1

Code? Nous sommes supposés lire dans ton esprit? Ceci est un exemple classique de la façon de ne pas poser une question. –

+1

s'il vous plaît mettre l'étiquette devoirs sur des questions sur les devoirs. En outre, il est beaucoup plus facile de vous aider avec votre code, si vous, nous savez, nous montrer le code :) – Affe

Répondre

3

Au plus bas niveau, votre code se résume probablement à quelque chose comme:

Thread1    Thread2 
-------    ------- 
do 1000 times   do 1000 times   
    get reg from [a]  get reg from [a] 
    reg = reg + 1   reg = reg - 1 
    store reg to [a]  store reg to [a] 

À cause de cela et le fait que les fils peuvent être arrêtés et ont commencé à tout moment de leur exécution, vous avez la possibilité de ceci:

Thread1    Thread2 
-------    ------- 
get reg from [a] (0) 
         get reg from [a] (0) 
reg = reg + 1 (1) 
         reg = reg - 1 (-1) 
         store reg to [a] (-1) 
store reg to [a] (1) 

vous pouvez voir que, bien que les deux fils ont terminé exactement l'un des mille cycles et vous attendez le compte soit nul, il est en fait l'un. Lorsque vous partagez des variables entre des threads d'exécution, vous devez vous assurer que les lectures, les écritures et les mises à jour sont atomiques (il y a des exceptions mais elles sont rares).

Pour ce faire, vous devez examiner les différentes opérations prévues dans votre environnement uniquement dans ce but (comme les fonctions de synchronisation dans la langue, mutex (sémaphores d'exclusion mutuelle) ou variables atomiques.

1

Regardez les raisons pour lesquelles les programmes multithread utilisent synchronization et vous devriez trouver à la fois la raison pour laquelle cela est un problème et comment le résoudre.

Pensez à ce que la concurrence:

Supposons que j'ai un point dans la mémoire appelée x.
J'ai une fonction appelée addOne qui prend la valeur de x l'incrémente de 1 et écrit cette valeur à x.
J'ai une autre fonction appelée subOne qui prend la valeur à x la décrémente de 1 et écrit cette valeur à x.

Que se passe-t-il si x = 10. addOne est appelée et prend cette valeur de 10 et commence à la traiter, mais avant d'écrire la valeur, subOne est appelée. subOne va prendre x comme 10, mais si nous y réfléchissions linéairement, la valeur devrait être 11. Selon la fonction qui écrit en dernier, la valeur sera différente.

Cela devrait vous aider à obtenir votre réponse, mais c'est quelque chose que vous devrez penser à vous-même. C'est le but des devoirs: apprendre.

+0

Merci qui a beaucoup aidé. Aussi, quand la sortie est nulle, je suppose que les threads se déroulent côte à côte sans s'interrompre les uns les autres? – bananamana

+2

Lorsque la sortie est zéro (sans aucune synchronisation), il s'agit essentiellement d'un coup de chance. Vous pouvez utiliser la synchronisation pour protéger les données d'être accédées par deux threads simultanément (ainsi le thread 1 peut incrémenter, puis le thread 2 peut décrémenter (mais pas toujours toujours d'avant en arrière)) et il devrait être 0. –

+1

Les instructions de synchronisation éviter que les conditions de course de se produire et incomplets, les transactions implicites :) –

1

Je ne voulez le gâcher pour vous, mais considérez ce qui se passerait si vous avez transformé votre méthode principale en ceci:

Avant de l'exécuter ... Pensez-y!:)

public static void main(String[] args) 
    { 
     Counter c = new Counter(); 

     Thread inc = new IncThread(c); 
     Thread dec = new DecThread(c); 

     inc.start(); 
     dec.start(); 

     System.out.println(c.getVal()); 
     try { Thread.sleep(1000); } catch(Exception e) { } 
     System.out.println(c.getVal()); 

    } 

EDIT: Pax mentionne quelque chose d'important. Ce n'est pas le même problème que mon exemple de code décrit, mais c'est quand même important.

Par ailleurs, voici la sortie quand je fais ça (La dernière exécution illustre le problème de Pax):

C:\Documents and Settings\<redacted>\My Documents>java Main 
1000 
0 

C:\Documents and Settings\<redacted>\My Documents>java Main 
82 
82 

C:\Documents and Settings\<redacted>\My Documents>java Main 
1000 
0 

C:\Documents and Settings\<redacted>\My Documents>java Main 
0 
5 
+0

OK donc je me suis rendu compte que la deuxième sortie serait nulle parce que vous êtes en train de mettre un fil en pause pour que l'autre finisse complètement. Et c'est probablement une question stupide, mais quel fil est sleep() appelé? – bananamana

+0

Rappelez-vous que vous avez trois threads impliqués. inc, dec et principal. Le sommeil est appelé sur le principal. Cependant, assurez-vous de lire mes éditions, et la réponse de PaxDiablo, car c'est aussi très important. – corsiKa

+0

J'ai juste essayé de l'exécuter encore quelques fois et parfois la deuxième sortie n'est pas zéro. Est-ce que j'ai râté quelque chose? – bananamana