Следует ли синхронизировать метод запуска? Почему или почему нет? - PullRequest
32 голосов
/ 06 сентября 2011

Я всегда думал, что синхронизация метода run в Java-классе, который реализует Runnable, является избыточной. Я пытаюсь выяснить, почему люди делают это:

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

Это кажется излишним и ненужным, поскольку они получают блокировку объекта для другого потока.Или, скорее, они делают явным, что только один поток имеет доступ к методу run ().Но поскольку это метод run, не является ли он собственным потоком?Следовательно, только он может получить доступ к самому себе, и ему не нужен отдельный механизм блокировки?

В сети я нашел предложение, что, синхронизируя метод run, вы можете потенциально создать очередь потока де-факто, например, выполнив это:

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

Я бы никогда этого не сделал лично, но это ставит вопрос о том, почему кто-либо синхронизирует метод run. Есть идеи, почему или почему не следует синхронизировать метод прогона?

Ответы [ 7 ]

31 голосов
/ 06 сентября 2011

Синхронизация run() метода Runnable абсолютно бессмысленна , если только вы не захотите разделить Runnable между несколькими потоками и , для которых вы хотите упорядочить выполнениеэти темы.Что в принципе является противоречием в терминах.

В теории существует еще один гораздо более сложный сценарий, в котором вы можете захотеть синхронизировать метод run(), который снова включает в себя разделение Runnable между несколькими потоками, но также создаетиспользование wait() и notify().Я никогда не сталкивался с этим в 21+ лет Java.

2 голосов
/ 06 сентября 2011

Почему? Минимальная дополнительная безопасность, и я не вижу ни одного правдоподобного сценария, где это имело бы значение.

Почему бы и нет? Это не стандартно. Если вы программируете как часть команды, когда какой-то другой участник увидит вашу синхронизированную run, он, вероятно, потратит 30 минут, пытаясь выяснить, что же такого особенного в вашей run или в среде, которую вы используете для запуска Runnable х

2 голосов
/ 06 сентября 2011

Преимущество использования synchronized void blah() над void blah() { synchronized(this) { имеет 1 преимущество, и ваш результирующий байт-код будет на 1 байт короче, поскольку синхронизация будет частью сигнатуры метода, а не самой операции. Это может повлиять на возможность встроить метод компилятором JIT. Кроме этого нет никакой разницы.

Лучшим вариантом является использование внутреннего private final Object lock = new Object(), чтобы предотвратить потенциальную блокировку вашего монитора. Это достигает того же самого результата без обратной стороны зла вне блокировки. У вас есть этот дополнительный байт, но это редко дает разницу.

Так что я бы сказал нет, не используйте ключевое слово synchronized в подписи. Вместо этого используйте что-то вроде

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

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

Редактировать в ответ на комментарий:

Рассмотрим, что делает синхронизация: она не позволяет другим потокам входить в тот же блок кода. Итак, представьте, что у вас есть класс, подобный приведенному ниже. Допустим, текущий размер равен 10. Кто-то пытается выполнить добавление, и оно вызывает изменение размера резервного массива. Пока они занимаются изменением размера массива, кто-то вызывает makeExactSize(5) в другом потоке. Теперь внезапно вы пытаетесь получить доступ к data[6], и это вас бомбит. Синхронизация должна предотвратить это. В многопоточных программах вам нужна просто НЕОБХОДИМАЯ синхронизация.

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;
    }
}
1 голос
/ 06 сентября 2011

Исходя из моего опыта, не полезно добавлять ключевое слово "synchronized" для метода run ().Если нам нужно синхронизировать несколько потоков или нам нужна потокобезопасная очередь, мы можем использовать более подходящие компоненты, такие как ConcurrentLinkedQueue.

0 голосов
/ 07 августа 2016

Просмотрите комментарии к коду и раскомментируйте и запустите различные блоки, чтобы четко увидеть разницу, заметим, что синхронизация заметок будет иметь значение только в том случае, если используется один и тот же запускаемый экземпляр, если каждый запущенный поток получает новый выполняемый, он не создает разница.

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 голосов
/ 06 сентября 2011

на самом деле действительно легко оправдать "синхронизировать или не синхронизировать"

если ваш вызов метода может изменить внутреннее состояние вашего объекта, тогда "синхронизировать" в противном случае нет необходимости

простой пример

public class Counter {

  private int count = 0; 

  public void incr() {
    count++;
  }

  public int getCount() {
    return count;
  }
}

в приведенном выше примере необходимо синхронизировать incr (), так как это изменит значение счетчика, тогда как синхронизация getCount () не нужна

однако есть еще один угловой случай, если счетчик java.lang.Long, Double, Object, то вам нужно объявить как

private volatile long count = 0;

чтобы убедиться, что обновление ссылки является атомарным

В основном это то, что вам нужно думать о 99% времени при работе с многопоточностью

0 голосов
/ 06 сентября 2011

Ну, вы можете теоретически вызвать сам метод run без проблем (в конце концов, он публичный). Но это не значит, что нужно это делать. Таким образом, в принципе, нет причин делать это, кроме добавления незначительных накладных расходов в поток, вызывающий run (). Хорошо, за исключением случаев, когда вы используете экземпляр несколько раз, вызывая new Thread - хотя я а) не уверен, что это законно с API потоков и б) кажется совершенно бесполезным.

Также ваш createThreadQueue не работает. synchronized в нестатическом методе синхронизируется с объектом экземпляра (т. Е. this), поэтому все три потока будут работать параллельно.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...