web-dev-qa-db-fra.com

Devez-vous synchroniser la méthode d'exécution? Pourquoi ou pourquoi pas?

J'ai toujours pensé que la synchronisation de la méthode run dans une classe Java implémentant Runnable était redondante. J'essaie de comprendre pourquoi les gens font ça:

public class ThreadedClass implements Runnable{
    //other stuff
    public synchronized void run(){
        while(true)
             //do some stuff in a thread
        }
    }
}

Cela semble redondant et inutile car ils obtiennent le verrou de l'objet pour un autre thread. Ou plutôt, ils expliquent qu'un seul thread a accès à la méthode run (). Mais puisque c'est la méthode d'exécution, n'est-ce pas lui-même son propre thread? Par conséquent, seul le système peut accéder à lui-même et ne nécessite pas de mécanisme de verrouillage distinct?

J'ai trouvé une suggestion en ligne selon laquelle, en synchronisant la méthode d'exécution, vous pourriez potentiellement créer une file de threads de facto, par exemple:

 public void createThreadQueue(){
    ThreadedClass a = new ThreadedClass();
    new Thread(a, "First one").start();
    new Thread(a, "Second one, waiting on the first one").start();
    new Thread(a, "Third one, waiting on the other two...").start();
 }

Je ne le ferais jamais personnellement, mais cela pose la question de savoir pourquoi quelqu'un synchroniserait la méthode d'exécution.Des idées pourquoi ou pourquoi ne pas synchroniser la méthode d'exécution?

31
MHP

La synchronisation de la méthode run() d'une Runnable est totalement inutile à moins que ne souhaite partager la Runnable entre plusieurs threads et pour séquencier l'exécution de ces threads. Ce qui est fondamentalement une contradiction dans les termes.

En théorie, il existe un autre scénario beaucoup plus compliqué dans lequel vous pouvez synchroniser la méthode run(), qui implique à nouveau le partage de Runnable entre plusieurs threads, mais utilise également les fonctions wait() et notify(). Je ne l'ai jamais rencontré depuis plus de 21 ans en Java.

28
user207421

Il y a un avantage à utiliser synchronized void blah() sur void blah() { synchronized(this) { et c'est le bytecode résultant qui sera plus court d'un octet, car la synchronisation fera partie de la signature de la méthode plutôt que d'une opération. Cela peut influer sur les chances d'insérer la méthode par le compilateur JIT. Autre que cela il n'y a pas de différence.

La meilleure option consiste à utiliser une private final Object lock = new Object() interne pour empêcher toute personne de verrouiller potentiellement votre moniteur. Le même résultat est obtenu sans les inconvénients du verrouillage extérieur maléfique. Vous avez cet octet supplémentaire, mais cela fait rarement une différence.

Donc, je dirais non, n'utilisez pas le mot clé synchronized dans la signature. Au lieu de cela, utilisez quelque chose comme

public class ThreadedClass implements Runnable{
    private final Object lock = new Object();

    public void run(){
        synchronized(lock) {
            while(true)
                 //do some stuff in a thread
            }
        }
    }
}

Modifier en réponse au commentaire:

Considérez ce que la synchronisation fait: cela empêche les autres threads d'entrer le même bloc de code. Alors, imaginez que vous ayez une classe comme celle ci-dessous. Disons que la taille actuelle est de 10. Quelqu'un essaie de faire un ajout et force le redimensionnement du tableau de sauvegarde. Alors qu'ils sont en train de redimensionner le tableau, quelqu'un appelle une makeExactSize(5) sur un autre thread. Tout à coup, vous essayez d'accéder à data[6] et il vous bombarde. La synchronisation est censée empêcher cela. Dans les programmes multithread, vous avez besoin simplement de la synchronisation NEED.

class Stack {
    int[] data = new int[10];
    int pos = 0;

    void add(int inc) {
        if(pos == data.length) {
            int[] tmp = new int[pos*2];
            for(int i = 0; i < pos; i++) tmp[i] = data[i];
            data = tmp;
        }
        data[pos++] = inc;
    }

    int remove() {
        return data[pos--];
    }

    void makeExactSize(int size) {
        int[] tmp = new int[size];
        for(int i = 0; i < size; i++) tmp[i] = data[i];
        data = tmp;
    }
}
2
corsiKa

Pourquoi? Un minimum de sécurité supplémentaire et je ne vois pas de scénario plausible où cela ferait une différence.

Pourquoi pas? Ce n'est pas standard. Si vous codez en équipe, lorsqu'un autre membre voit votre run synchronisée, il perdra probablement 30 minutes à essayer de comprendre ce qui est si spécial avec votre run ou avec la structure que vous utilisez pour exécuter les Runnable.

2
toto2

D'après mon expérience, il n'est pas utile d'ajouter le mot clé "synchronized" à la méthode run (). Si nous avons besoin de synchroniser plusieurs threads ou si nous avons besoin d'une file d'attente sécurisée pour les threads, nous pouvons utiliser des composants plus appropriés, tels que ConcurrentLinkedQueue.

1
James Gan

Eh bien, vous pouvez théoriquement appeler la méthode run elle-même sans problème (après tout, elle est publique). Mais cela ne signifie pas qu'il faille le faire. Donc, fondamentalement, il n’ya aucune raison de le faire, mis à part l’ajout d’une surcharge négligeable au thread appelant run (). Eh bien, sauf si vous utilisez l'instance plusieurs fois en appelant new Thread - bien que je sois a) pas sûr que ce soit légal avec l'API de threading et b) semble complètement inutile.

De plus, votre createThreadQueue ne fonctionne pas. synchronized sur une méthode non statique se synchronise sur l'objet d'instance (c'est-à-dire this), de sorte que les trois threads s'exécutent en parallèle.

0
Voo

Parcourez les commentaires de code et supprimez le commentaire, puis exécutez les différents blocs pour voir clairement la différence. Notez que la synchronisation n'aura de différence que si la même instance exécutable est utilisée. Si chaque thread démarré obtient un nouveau exécutable, cela ne fera aucune différence.

class Kat{

public static void main(String... args){
  Thread t1;
  // MyUsualRunnable is usual stuff, only this will allow concurrency
  MyUsualRunnable m0 = new MyUsualRunnable();
  for(int i = 0; i < 5; i++){
  t1 = new Thread(m0);//*imp*  here all threads created are passed the same runnable instance
  t1.start();
  }

  // run() method is synchronized , concurrency killed
  // uncomment below block and run to see the difference

  MySynchRunnable1 m1 = new MySynchRunnable1();
  for(int i = 0; i < 5; i++){
  t1 = new Thread(m1);//*imp*  here all threads created are passed the same runnable instance, m1
  // if new insances of runnable above were created for each loop then synchronizing will have no effect

  t1.start();
}

  // run() method has synchronized block which lock on runnable instance , concurrency killed
  // uncomment below block and run to see the difference
  /*
  MySynchRunnable2 m2 = new MySynchRunnable2();
  for(int i = 0; i < 5; i++){
  // if new insances of runnable above were created for each loop then synchronizing will have no effect
  t1 = new Thread(m2);//*imp*  here all threads created are passed the same runnable instance, m2
  t1.start();
}*/

}
}

class MyUsualRunnable implements Runnable{
  @Override
  public void  run(){
    try {Thread.sleep(1000);} catch (InterruptedException e) {}
}
}

class MySynchRunnable1 implements Runnable{
  // this is implicit synchronization
  //on the runnable instance as the run()
  // method is synchronized
  @Override
  public synchronized void  run(){
    try {Thread.sleep(1000);} catch (InterruptedException e) {}
}
}

class MySynchRunnable2 implements Runnable{
  // this is explicit synchronization
  //on the runnable instance
  //inside the synchronized block
  // MySynchRunnable2 is totally equivalent to MySynchRunnable1
  // usually we never synchronize on this or synchronize the run() method
  @Override
  public void  run(){
    synchronized(this){
    try {Thread.sleep(1000);} catch (InterruptedException e) {}
  }
}
}
0
Anirtak Varma