Подтвердить что ты не робот

Следует ли синхронизировать метод запуска? Почему или почему нет?

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

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

Кажется излишним и ненужным, поскольку они получают блокировку объекта для другого потока. Вернее, они делают явным, что только один поток имеет доступ к методу 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. Любые идеи, почему или почему не следует синхронизировать метод запуска?

4b9b3361

Ответ 1

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

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

Ответ 2

Существует 1 преимущество использования synchronized void blah() над void blah() { synchronized(this) {, и ваш результирующий байт-код будет на 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], и он бомбит вас. Предполагается, что синхронизация предотвратит это. В многопоточных программах вам нужна просто 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;
    }
}

Ответ 3

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

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

Ответ 4

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

Ответ 5

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

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

Ответ 6

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

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) {}
  }
}
}

Ответ 7

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

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

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

public class Counter {

  private int count = 0; 

  public void incr() {
    count++;
  }

  public int getCount() {
    return count;
  }
}

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

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

private volatile long count = 0;

чтобы обновление ref было атомарным

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