wake-up-neo.com

Solltest du die Laufmethode synchronisieren? Warum oder warum nicht?

Ich habe immer gedacht, dass das Synchronisieren der run-Methode in einer Java-Klasse, die Runnable implementiert, redundant ist. Ich versuche herauszufinden, warum die Leute das tun:

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

Es scheint überflüssig und unnötig zu sein, da sie die Sperre des Objekts für einen anderen Thread erhalten. Oder sie machen deutlich, dass nur ein Thread Zugriff auf die run () -Methode hat. Aber da es sich um die Run-Methode handelt, ist es nicht selbst ein eigener Thread? Daher kann nur er auf sich selbst zugreifen und benötigt keinen separaten Verriegelungsmechanismus.

Ich habe online einen Vorschlag gefunden, dass Sie durch Synchronisieren der Run-Methode möglicherweise eine de-facto-Thread-Warteschlange erstellen könnten, indem Sie Folgendes tun:

 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();
 }

Ich würde das niemals persönlich tun, aber es stellt sich die Frage, warum jemand die Run-Methode synchronisieren würde.Irgendwelche Ideen warum oder warum sollte man die Run-Methode nicht synchronisieren?

31
MHP

Das Synchronisieren der run() -Methode einer Runnable ist völlig sinnlos es sei denn Sie möchten die Runnable unter mehreren Threads teilen - und Sie möchten die Ausführung dieser Threads sequenzieren. Was im Grunde ein Widerspruch ist.

Theoretisch gibt es ein anderes, viel komplizierteres Szenario, in dem Sie die run()-Methode synchronisieren möchten. Dazu gehört wiederum das gemeinsame Verwenden der Runnable zwischen mehreren Threads, wobei jedoch wait() und notify() verwendet werden. Ich habe es in 21+ Jahren Java noch nie erlebt.

28
user207421

Die Verwendung von synchronized void blah() gegenüber void blah() { synchronized(this) { bietet einen Vorteil, und der resultierende Bytecode wird um 1 Byte kürzer sein, da die Synchronisierung Teil der Methodensignatur und nicht eine Operation selbst ist. Dies kann die Möglichkeit beeinflussen, die Methode durch den JIT-Compiler inline zu integrieren. Ansonsten gibt es keinen Unterschied.

Die beste Option ist die Verwendung einer internen private final Object lock = new Object(), um zu verhindern, dass jemand Ihren Monitor blockiert. Es erzielt das gleiche Ergebnis ohne den Nachteil der bösen Außenverriegelung. Sie haben dieses zusätzliche Byte, aber es macht selten einen Unterschied.

Also würde ich nein sagen, verwenden Sie nicht das Schlüsselwort synchronized in der Signatur. Verwenden Sie stattdessen etwas wie

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

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

Als Antwort auf Kommentar bearbeiten:

Berücksichtigen Sie, was die Synchronisation bewirkt: Sie verhindert, dass andere Threads in denselben Codeblock gehen. Stellen Sie sich vor, Sie haben eine Klasse wie die unten. Angenommen, die aktuelle Größe ist 10. Jemand versucht, eine Addition auszuführen, und erzwingt eine Größenänderung des Hintergrundarrays. Während sie gerade die Größe des Arrays ändern, ruft jemand eine makeExactSize(5) in einem anderen Thread auf. Jetzt versuchen Sie plötzlich, auf data[6] zuzugreifen, und es bombardiert Sie. Die Synchronisation soll das verhindern. In Multithread-Programmen benötigen Sie einfach eine NEED-Synchronisierung.

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

Warum? Minimale zusätzliche Sicherheit und ich sehe kein plausibles Szenario, in dem es einen Unterschied machen würde.

Warum nicht? Das ist kein Standard. Wenn Sie als Teil eines Teams programmieren, wenn ein anderes Mitglied Ihre synchronisierte run sieht, wird er wahrscheinlich 30 Minuten damit verschwenden, herauszufinden, was so besonders ist, entweder mit Ihrer run oder mit dem Framework, das Sie zum Ausführen der Runnable verwenden.

2
toto2

Aus meiner Erfahrung ist es nicht sinnvoll, der run () - Methode ein "synchronisiertes" Schlüsselwort hinzuzufügen. Wenn wir mehrere Threads synchronisieren müssen oder eine Thread-sichere Warteschlange benötigen, können wir geeignetere Komponenten wie ConcurrentLinkedQueue verwenden.

1
James Gan

Nun, man könnte die run-Methode theoretisch ohne Probleme aufrufen (schließlich ist es öffentlich). Das heißt aber nicht, dass man es tun sollte. Im Grunde gibt es keinen Grund, dies zu tun, abgesehen davon, dass der Thread, der run () aufruft, ein vernachlässigbarer Overhead hinzugefügt wird. Außer, wenn Sie die Instanz mehrfach verwenden, rufen Sie new Thread auf - obwohl ich a) nicht sicher bin, ob dies mit der Threading-API zulässig ist und b) völlig nutzlos erscheint.

Auch Ihre createThreadQueue funktioniert nicht. synchronized bei einer nicht statischen Methode synchronisiert das Instanzobjekt (dh this), sodass alle drei Threads parallel ausgeführt werden.

0
Voo

Durchlaufen Sie die Codekommentare und heben Sie die Kommentarzeichen auf und führen Sie die verschiedenen Blöcke aus, um den Unterschied deutlich zu sehen. Beachten Sie, dass die Synchronisierung nur dann einen Unterschied hat, wenn dieselbe ausführbare Instanz verwendet wird. Wenn jeder gestartete Thread ein neues ausführbares Element erhält, ist dies ohne Unterschied.

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