Есть ли преимущество в использовании синхронизированного метода вместо синхронизированного блока?

401

Кто-нибудь может сказать мне преимущество синхронизированного метода перед синхронизированным блоком с примером?

воин
источник
1
Точная копия: stackoverflow.com/questions/442564/… и stackoverflow.com/questions/416183/…
Ювал Адам
1
@cletus этот вопрос полностью отличается от stackoverflow.com/questions/442564/…
Юкио Фукузава

Ответы:

431

Может кто-нибудь сказать мне преимущество синхронизированного метода над синхронизированным блоком на примере? Спасибо.

Нет явного преимущества использования синхронизированного метода над блоком.

Возможно, единственным (но я бы не назвал это преимуществом) является то, что вам не нужно включать ссылку на объект this.

Метод:

public synchronized void method() { // blocks "this" from here.... 
    ...
    ...
    ...
} // to here

Блок:

public void method() { 
    synchronized( this ) { // blocks "this" from here .... 
        ....
        ....
        ....
    }  // to here...
}

Видеть? Никаких преимуществ вообще.

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

Для сравнения:

// locks the whole object
... 
private synchronized void someInputRelatedWork() {
    ... 
}
private synchronized void someOutputRelatedWork() {
    ... 
}

против

// Using specific locks
Object inputLock = new Object();
Object outputLock = new Object();

private void someInputRelatedWork() {
    synchronized(inputLock) { 
        ... 
    } 
}
private void someOutputRelatedWork() {
    synchronized(outputLock) { 
        ... 
    }
}

Также, если метод растет, вы все равно можете держать синхронизированный раздел отделенным:

 private void method() {
     ... code here
     ... code here
     ... code here
    synchronized( lock ) { 
        ... very few lines of code here
    }
     ... code here
     ... code here
     ... code here
     ... code here
}
OscarRyz
источник
44
Преимущество для потребителя API заключается в том, что использование ключевого слова synchronized в объявлении метода также явно объявляет, что метод синхронизируется на экземпляре объекта и (предположительно) поточно-ориентирован.
Скрабби
59
Я знаю, что это старый вопрос, но в некоторых кругах синхронизация по «этому» считается анти-паттерном. Непреднамеренное последствие заключается в том, что за пределами класса кто-то может заблокировать ссылку на объект, равную «this», и не дать другим потокам проходить барьеры внутри класса, потенциально создавая ситуацию взаимоблокировки. Создание "частного конечного объекта = новый объект ();" переменная исключительно для целей блокировки является часто используемым решением. Вот еще один вопрос, касающийся непосредственно этой проблемы.
justin.hughey
30
msgstr "синхронизация метода заблокирует весь класс". Это не правильно. Он блокирует не весь класс, а полный экземпляр. Несколько объектов из одного класса содержат все свои собственные блокировки. :)
Приветсвенно
4
Что-то интересное в этом заключается в том, что использование синхронизированного метода приведет к тому, что сгенерированный байт-код будет иметь на 1 команду меньше, поскольку у методов есть синхронизированный бит, запеченный в их сигнатуре. Поскольку длина байт-кода является фактором, определяющим, является ли метод встроенным, перемещение решения в сигнатуру метода может быть различием в принятии решения. В любом случае в теории. Я бы не стал принимать решение о разработке на основе сохранения одной инструкции байт-кода, что кажется ужасной идеей. Но все - таки, это есть разница. =)
corsiKa
2
@corsiKa: вы сохраняете более одной инструкции. synchronizedБлок осуществляется с помощью двух команд, monitorenterи monitorexit, плюс обработчик исключений , который гарантирует , что monitorexitназывается даже в исключительном случае. Это все сохраняется при использовании synchronizedметода.
Хольгер
139

Единственная реальная разница в том, что синхронизированный блок может выбирать, на каком объекте он синхронизируется. Синхронизированный метод может использовать только 'this'(или соответствующий экземпляр класса для синхронизированного метода класса). Например, они семантически эквивалентны:

synchronized void foo() {
  ...
}

void foo() {
    synchronized (this) {
      ...
    }
}

Последний является более гибким, поскольку он может конкурировать за связанную блокировку любого объекта, часто переменной-члена. Это также более детально, потому что у вас может быть параллельный код, выполняющийся до и после блока, но все же внутри метода. Конечно, вы могли бы также легко использовать синхронизированный метод путем рефакторинга параллельного кода в отдельные несинхронизированные методы. Используйте то, что делает код более понятным.

jcrossley3
источник
Последнее также может быть полезным, если не весь код в foo () необходимо синхронизировать.
Эван
1
Это правда, но не то, что «Воин» спросил: «Преимущества синхронизированного метода» здесь нет.
OscarRyz
76

Синхронизированный метод

Плюсы:

  • Ваша IDE может указывать синхронизированные методы.
  • Синтаксис более компактен.
  • Заставляет разделять синхронизированные блоки на отдельные методы.

Минусы:

  • Синхронизируется с этим и поэтому позволяет посторонним синхронизироваться с ним тоже.
  • Труднее переместить код за пределы синхронизированного блока.

Синхронизированный блок

Плюсы:

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

Минусы:

  • Синтаксис более сложен и поэтому делает код труднее для чтения.

Лично я предпочитаю использовать синхронизированные методы с классами, ориентированными только на то, что требует синхронизации. Такой класс должен быть как можно меньше, и поэтому должно быть легко проверить синхронизацию. Другим не нужно заботиться о синхронизации.

ины
источник
Когда вы говорите «оставаться внутри класса», вы имеете в виду «оставаться внутри объекта » или я что-то упускаю?
OldPeculier
36

Основное отличие состоит в том, что если вы используете синхронизированный блок, вы можете заблокировать объект, отличный от этого, что позволяет быть более гибким.

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

Object writeLock = new Object();

И теперь каждый раз, когда производители хотят добавить новое сообщение, мы просто фиксируем это:

synchronized(writeLock){
  // do something
}

Таким образом, потребители все еще могут читать, а производители будут заблокированы.

cdecker
источник
2
Ваш пример ограничен неразрушающим чтением. Если чтение удаляет сообщение из очереди, произойдет сбой, если это будет сделано в тот момент, когда производитель записывает в очередь.
выступление
30

Синхронизированный метод

Синхронизированные методы имеют два эффекта.
Во-первых, когда один поток выполняет синхронизированный метод для объекта, все остальные потоки, которые вызывают синхронизированные методы для того же блока объекта (приостанавливают выполнение), пока первый поток не завершится с объектом.

Во-вторых, при выходе из синхронизированного метода он автоматически устанавливает отношение «до и после» с любым последующим вызовом синхронизированного метода для того же объекта. Это гарантирует, что изменения состояния объекта видны всем потокам.

Обратите внимание, что конструкторы не могут быть синхронизированы - использование ключевого слова synchronized с конструктором является синтаксической ошибкой. Синхронизация конструкторов не имеет смысла, потому что только поток, который создает объект, должен иметь доступ к нему во время его конструирования.

Синхронизированный оператор

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

Q: Внутренние блокировки и синхронизация Синхронизация строится вокруг внутренней сущности, известной как внутренняя блокировка или блокировка монитора. (Спецификация API часто именует эту сущность просто как «монитор».) Внутренние блокировки играют роль в обоих аспектах синхронизации: обеспечение исключительного доступа к состоянию объекта и установление отношений «до и после», которые важны для видимости.

Каждый объект имеет встроенную блокировку, связанную с ним. По соглашению поток, которому требуется исключительный и согласованный доступ к полям объекта, должен получить внутреннюю блокировку объекта перед тем, как получить к ним доступ, а затем снять внутреннюю блокировку, когда это будет сделано с ними. Говорят, что поток владеет внутренней блокировкой между моментом, когда он получил блокировку и снял ее. Пока потоку принадлежит внутренняя блокировка, никакой другой поток не может получить такую ​​же блокировку. Другой поток заблокируется, когда попытается получить блокировку.

package test;

public class SynchTest implements Runnable {  
    private int c = 0;

    public static void main(String[] args) {
        new SynchTest().test();
    }

    public void test() {
        // Create the object with the run() method
        Runnable runnable = new SynchTest();
        Runnable runnable2 = new SynchTest();
        // Create the thread supplying it with the runnable object
        Thread thread = new Thread(runnable,"thread-1");
        Thread thread2 = new Thread(runnable,"thread-2");
//      Here the key point is passing same object, if you pass runnable2 for thread2,
//      then its not applicable for synchronization test and that wont give expected
//      output Synchronization method means "it is not possible for two invocations
//      of synchronized methods on the same object to interleave"

        // Start the thread
        thread.start();
        thread2.start();
    }

    public synchronized  void increment() {
        System.out.println("Begin thread " + Thread.currentThread().getName());
        System.out.println(this.hashCode() + "Value of C = " + c);
//      If we uncomment this for synchronized block, then the result would be different
//      synchronized(this) {
            for (int i = 0; i < 9999999; i++) {
                c += i;
            }
//      }
        System.out.println("End thread " + Thread.currentThread().getName());
    }

//    public synchronized void decrement() {
//        System.out.println("Decrement " + Thread.currentThread().getName());
//    }

    public int value() {
        return c;
    }

    @Override
    public void run() {
        this.increment();
    }
}

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

sudheer
источник
10
+1 за то, что единственный, кто упомянул, что конструкторы не могут быть синхронизированы . То есть в конструкторе у вас действительно есть только одна опция: синхронизированные блоки.
ef2011
Я проверил ваш код в соответствии с указаниями, но C всегда равен 0, затем -2024260031 и единственное, что изменяет его хеш-код. Какое поведение должно быть видно?
Джастин Джонсон
Вы должны были процитировать статьи ниже, из которых было предоставлено содержание: docs.oracle.com/javase/tutorial/essential/concurrency/… и docs.oracle.com/javase/tutorial/essential/concurrency/…
Равиндра Бабу
29

Примечание: статические синхронизированные методы и блоки работают с объектом Class.

public class MyClass {
   // locks MyClass.class
   public static synchronized void foo() {
// do something
   }

   // similar
   public static void foo() {
      synchronized(MyClass.class) {
// do something
      }
   }
}
Питер Лори
источник
18

Когда компилятор Java преобразует ваш исходный код в байт-код, он обрабатывает синхронизированные методы и синхронизированные блоки совершенно по-разному.

Когда JVM выполняет синхронизированный метод, исполняющий поток идентифицирует, что в структуре method_info метода установлен флаг ACC_SYNCHRONIZED, затем он автоматически получает блокировку объекта, вызывает метод и снимает блокировку. Если возникает исключение, поток автоматически снимает блокировку.

Синхронизация блока метода, с другой стороны, обходит встроенную поддержку JVM для получения блокировки объекта и обработки исключений и требует, чтобы функциональность была явно записана в байтовом коде. Если вы прочитаете байт-код для метода с синхронизированным блоком, вы увидите более десятка дополнительных операций для управления этой функциональностью.

Здесь показаны вызовы для генерации синхронизированного метода и синхронизированного блока:

public class SynchronizationExample {
    private int i;

    public synchronized int synchronizedMethodGet() {
        return i;
    }

    public int synchronizedBlockGet() {
        synchronized( this ) {
            return i;
        }
    }
}

synchronizedMethodGet()Метод генерирует следующий байт - код:

0:  aload_0
1:  getfield
2:  nop
3:  iconst_m1
4:  ireturn

И вот байт-код из synchronizedBlockGet()метода:

0:  aload_0
1:  dup
2:  astore_1
3:  monitorenter
4:  aload_0
5:  getfield
6:  nop
7:  iconst_m1
8:  aload_1
9:  monitorexit
10: ireturn
11: astore_2
12: aload_1
13: monitorexit
14: aload_2
15: athrow

Одно существенное различие между синхронизированным методом и блоком состоит в том, что синхронизированный блок обычно уменьшает область блокировки. Поскольку объем блокировки обратно пропорционален производительности, всегда лучше блокировать только критическую часть кода. Один из лучших примеров использования синхронизированного блока - это двойная проверка блокировки в шаблоне Singleton, где вместо блокировки целого getInstance()метода мы блокируем только критическую часть кода, которая используется для создания экземпляра Singleton. Это резко повышает производительность, потому что блокировка требуется только один или два раза.

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

Мухаммед Адиль
источник
1
Если мы посмотрим на синхронизированный метод байт-кода, то байт-код более компактен и прост, так почему его не быстрее, чем синхронизированный блок?
eatSleepCode
@eatSleepCode Обратите внимание, что это байт-код, который далее «компилируется» JVM. JVM добавит необходимое monitorenterи monitorexitперед запуском кода.
Филипп Коулинг
12

Чаще всего я использую это для синхронизации доступа к списку или карте, но я не хочу блокировать доступ ко всем методам объекта.

В следующем коде один поток, модифицирующий список, не будет блокировать ожидание потока, который модифицирует карту. Если бы методы были синхронизированы на объекте, то каждый метод должен был бы ждать, даже если изменения, которые они делают, не будут конфликтовать.

private List<Foo> myList = new ArrayList<Foo>();
private Map<String,Bar) myMap = new HashMap<String,Bar>();

public void put( String s, Bar b ) {
  synchronized( myMap ) {
    myMap.put( s,b );
    // then some thing that may take a while like a database access or RPC or notifying listeners
  }
}

public void hasKey( String s, ) {
  synchronized( myMap ) {
    myMap.hasKey( s );
  }
}

public void add( Foo f ) {
  synchronized( myList ) {
    myList.add( f );
// then some thing that may take a while like a database access or RPC or notifying listeners
  }
}

public Thing getMedianFoo() {
  Foo med = null;
  synchronized( myList ) {
    Collections.sort(myList);
    med = myList.get(myList.size()/2); 
  }
  return med;
}
Clint
источник
7

С синхронизированными блоками у вас может быть несколько синхронизаторов, так что одновременно может происходить несколько одновременных, но не конфликтующих вещей.

Пол Томблин
источник
6

Синхронизированные методы можно проверить с помощью API отражения. Это может быть полезно для тестирования некоторых контрактов, например, все методы в модели синхронизированы .

Следующий фрагмент выводит все синхронизированные методы Hashtable:

for (Method m : Hashtable.class.getMethods()) {
        if (Modifier.isSynchronized(m.getModifiers())) {
            System.out.println(m);
        }
}
Kojotak
источник
5

Важное замечание по использованию синхронизированного блока: осторожно, что вы используете в качестве объекта блокировки!

Фрагмент кода из user2277816 выше иллюстрирует этот момент в том смысле, что ссылка на строковый литерал используется в качестве объекта блокировки. Поймите, что строковые литералы автоматически внедряются в Java, и вы должны начать видеть проблему: каждый фрагмент кода, который синхронизируется с литералом «блокировка», разделяет одну и ту же блокировку! Это может легко привести к взаимоблокировке с совершенно не связанными частями кода.

С объектами нужно быть осторожнее. Примитивы в штучной упаковке также представляют опасность, поскольку методы autoboxing и valueOf могут повторно использовать одни и те же объекты в зависимости от значения.

Для получения дополнительной информации см .: https://www.securecoding.cert.org/confluence/display/java/LCK01-J.+Do+not+synchronize+on+objects+that+may+be+reused

Сорен Бойзен
источник
5

Часто использование блокировки на уровне метода слишком грубо. Зачем блокировать кусок кода, который не имеет доступа к каким-либо общим ресурсам, блокируя весь метод. Поскольку каждый объект имеет блокировку, вы можете создавать фиктивные объекты для реализации синхронизации на уровне блоков. Уровень блока более эффективен, потому что он не блокирует весь метод.

Вот пример

Уровень метода

class MethodLevel {

  //shared among threads
SharedResource x, y ;

public void synchronized method1() {
   //multiple threads can't access
}
public void synchronized method2() {
  //multiple threads can't access
}

 public void method3() {
  //not synchronized
  //multiple threads can access
 }
}

Уровень блока

class BlockLevel {
  //shared among threads
  SharedResource x, y ;

  //dummy objects for locking
  Object xLock = new Object();
  Object yLock = new Object();

    public void method1() {
     synchronized(xLock){
    //access x here. thread safe
    }

    //do something here but don't use SharedResource x, y
    // because will not be thread-safe
     synchronized(xLock) {
       synchronized(yLock) {
      //access x,y here. thread safe
      }
     }

     //do something here but don't use SharedResource x, y
     //because will not be thread-safe
    }//end of method1
 }

[Редактировать]

Для Collectionкак Vectorи Hashtableони синхронизированы , когда ArrayListили HashMapнет , и вы должны набора синхронизированы ключевым слово или запускайте Collections синхронизированного метода:

Map myMap = Collections.synchronizedMap (myMap); // single lock for the entire map
List myList = Collections.synchronizedList (myList); // single lock for the entire list
Максим Шустин
источник
5

Единственное отличие: синхронизированные блоки допускают гранулярную блокировку в отличие от синхронизированного метода

В основном synchronizedблок или методы использовались для написания поточно-безопасного кода, избегая ошибок несогласованности памяти.

Этот вопрос очень старый и многое изменилось за последние 7 лет. Новые конструкции программирования были введены для обеспечения безопасности потоков.

Вы можете достичь безопасности потоков, используя расширенный API параллелизма вместо synchroniedблоков. На этой странице документации представлены хорошие программные конструкции для обеспечения безопасности потоков.

Объекты блокировки поддерживают идиомы блокировки, которые упрощают многие параллельные приложения.

Исполнители определяют высокоуровневый API для запуска и управления потоками. Реализации исполнителя, предоставляемые java.util.concurrent, обеспечивают управление пулом потоков, подходящее для крупномасштабных приложений.

Параллельные сборы упрощают управление большими коллекциями данных и могут значительно сократить потребность в синхронизации.

Атомарные переменные имеют функции, которые минимизируют синхронизацию и помогают избежать ошибок согласованности памяти.

ThreadLocalRandom (в JDK 7) обеспечивает эффективную генерацию псевдослучайных чисел из нескольких потоков.

Лучшая замена синхронизированным - ReentrantLock , который использует LockAPI

Блокировка взаимного исключения с повторным входом с тем же базовым поведением и семантикой, что и для неявной блокировки монитора, доступ к которой осуществляется с помощью синхронизированных методов и операторов, но с расширенными возможностями.

Пример с замками:

class X {
   private final ReentrantLock lock = new ReentrantLock();
   // ...

   public void m() {
     lock.lock();  // block until condition holds
     try {
       // ... method body
     } finally {
       lock.unlock()
     }
   }
 }

См. Также пакеты java.util.concurrent и java.util.concurrent.atomic для других программных конструкций.

См. Также этот связанный вопрос:

Синхронизация против блокировки

Равиндра Бабу
источник
4

Синхронизированный метод используется для блокировки всех объектов Синхронизированный блок используется для блокировки определенного объекта

Кишор
источник
3

В общем, это в основном то же самое, кроме того, что они явно относятся к монитору объекта, который используется против неявного объекта this. Недостатком синхронизированных методов, который, я думаю, иногда упускают из виду, является то, что при использовании ссылки this для синхронизации вы оставляете открытой возможность блокировки внешних объектов на одном и том же объекте. Это может быть очень тонкая ошибка, если вы столкнетесь с ней. Синхронизация по внутреннему явному объекту или другому существующему полю может избежать этой проблемы, полностью инкапсулируя синхронизацию.

Алекс Миллер
источник
2

Как уже было сказано, синхронизированный блок может использовать пользовательскую переменную в качестве объекта блокировки, когда синхронизированная функция использует только «this». И, конечно, вы можете манипулировать областями своей функции, которые должны быть синхронизированы. Но все говорят, что нет никакой разницы между синхронизированной функцией и блоком, который покрывает всю функцию, используя «this» в качестве объекта блокировки. Это не так, разница в байт-коде, который будет сгенерирован в обеих ситуациях. В случае использования синхронизированного блока должна быть выделена локальная переменная, которая содержит ссылку на «это». И как результат, у нас будет немного больший размер для функции (не имеет значения, если у вас всего несколько функций).

Более подробное объяснение разницы вы можете найти здесь: http://www.artima.com/insidejvm/ed2/threadsynchP.html

Роман
источник
2

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

Пример :

    Class Example {
    String test = "abc";
    // lock will be acquired on String  test object.
    synchronized (test) {
        // do something
    }

   lock will be acquired on Example Object
   public synchronized void testMethod() {
     // do some thing
   } 

   }
Срину Ярру
источник
2

Я знаю, что это старый вопрос, но, прочитав ответы здесь, я не заметил, чтобы кто-то упомянул, что иногда synchronizedметод может быть неправильной блокировкой.
Из Java-параллелизма на практике (стр. 72):

public class ListHelper<E> {
  public List<E> list = Collections.syncrhonizedList(new ArrayList<>());
...

public syncrhonized boolean putIfAbsent(E x) {
 boolean absent = !list.contains(x);
if(absent) {
 list.add(x);
}
return absent;
}

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

public boolean putIfAbsent(E x) {
 synchronized(list) {
  boolean absent = !list.contains(x);
  if(absent) {
    list.add(x);
  }
  return absent;
}
}

Приведенный выше код блокирует все потоки, пытающиеся изменить список, от изменения списка до завершения синхронизированного блока.

aarbor
источник
читая эту книгу в данный момент ... мне интересно ... если бы этот список был приватным, а не публичным и имел только метод putIfAbsent, было бы достаточно синхронизировать (это), верно? проблема в том, что список может быть изменен вне этого ListHelper, а?
DTC
@dtc да, если список был закрытым и не просочился нигде в классе, этого было бы достаточно, если вы пометили все остальные методы в классе, которые также изменяют список как синхронизированный. Однако блокировка всего метода вместо только одного Listможет привести к проблемам с производительностью, если существует журнал кода, который не обязательно должен быть синхронизирован
aarbor
в этом есть смысл. Большое спасибо за ответ! Теб, я нашел книгу весьма полезной в расширении моих знаний и о том, как подойти к многопоточности, но она также
dtc
2

На практике преимущество синхронизированных методов перед синхронизированными блоками заключается в том, что они более устойчивы к идиотам; поскольку вы не можете выбрать произвольный объект для блокировки, вы не можете неправильно использовать синтаксис синхронизированного метода, чтобы делать глупые вещи, такие как блокировка строкового литерала или блокировка содержимого изменяемого поля, которое изменяется из-под потоков.

С другой стороны, с синхронизированными методами вы не можете защитить блокировку от получения любым потоком, который может получить ссылку на объект.

Таким образом, использование синхронизированного в качестве модификатора в методах лучше для защиты ваших сотрудников-коров от вреда для себя, в то время как использование синхронизированных блоков в сочетании с частными объектами окончательной блокировки лучше для защиты вашего собственного кода от сотрудников-коров.

Натан Хьюз
источник
1

Из резюме спецификации Java: http://www.cs.cornell.edu/andru/javaspec/17.doc.html

Синхронизированный оператор (§14.17) вычисляет ссылку на объект; затем он пытается выполнить действие блокировки для этого объекта и не продолжается, пока действие блокировки не будет успешно завершено. ...

Синхронизированный метод (§8.4.3.5) автоматически выполняет действие блокировки при его вызове; его тело не будет выполнено, пока действие блокировки не будет успешно завершено. Если метод является методом экземпляра , он блокирует блокировку, связанную с экземпляром, для которого он был вызван (то есть объектом, который будет известен как этот во время выполнения тела метода). Если метод является статическим , он блокирует блокировку, связанную с объектом Class, который представляет класс, в котором определен метод. ...

Основываясь на этих описаниях, я бы сказал, что большинство предыдущих ответов верны, и синхронизированный метод может быть особенно полезен для статических методов, где в противном случае вам пришлось бы выяснить, как получить объект «Class», представляющий класс, в котором этот метод был определены «.

Изменить: я первоначально думал, что это были цитаты из фактической спецификации Java. Уточнил, что эта страница является лишь кратким описанием / объяснением спецификации

Иосия Йодер
источник
1

TLDR; Ни используйте synchronizedмодификатор, ни synchronized(this){...}выражение, но synchronized(myLock){...}где myLockнаходится конечное поле экземпляра, содержащее закрытый объект.


Разница между использованием synchronizedмодификатора в объявлении метода и synchronized(..){ }выражением в теле метода заключается в следующем:

  • synchronizedМодификатор указанный на подписи методы
    1. виден в сгенерированном JavaDoc,
    2. программно определяется с помощью отражения при тестировании модификатора метода для Modifier.SYNCHRONIZED ,
    3. требует меньше печатания и отступа по сравнению с synchronized(this) { .... }, и
    4. (в зависимости от вашей IDE) виден в схеме класса и дополнения кода,
    5. использует thisобъект в качестве блокировки при объявлении нестатического метода или включающего класса при объявлении статического метода.
  • synchronized(...){...}Выражение позволяет
    1. только синхронизировать выполнение частей тела метода,
    2. для использования в конструкторе или ( статическом ) блоке инициализации,
    3. выбрать объект блокировки, который контролирует синхронизированный доступ.

Однако использование synchronizedмодификатора или synchronized(...) {...}с использованием thisв качестве объекта блокировки (как в synchronized(this) {...}) имеет тот же недостаток. Оба используют свой собственный экземпляр в качестве объекта блокировки для синхронизации. Это опасно, потому что не только сам объект, но и любой другой внешний объект / код, который содержит ссылку на этот объект, также может использовать его в качестве блокировки синхронизации с потенциально серьезными побочными эффектами (снижение производительности и взаимоблокировки ).

Поэтому рекомендуется не использовать ни synchronizedмодификатор, ни synchronized(...)выражение в сочетании с thisобъектом блокировки, а с объектом блокировки, закрытым для этого объекта. Например:

public class MyService {
    private final lock = new Object();

    public void doThis() {
       synchronized(lock) {
          // do code that requires synchronous execution
        }
    }

    public void doThat() {
       synchronized(lock) {
          // do code that requires synchronous execution
        }
    }
}

Вы также можете использовать несколько объектов блокировки, но необходимо соблюдать особую осторожность, чтобы гарантировать, что это не приведет к взаимным блокировкам при использовании вложенных.

public class MyService {
    private final lock1 = new Object();
    private final lock2 = new Object();

    public void doThis() {
       synchronized(lock1) {
          synchronized(lock2) {
              // code here is guaranteed not to be executes at the same time
              // as the synchronized code in doThat() and doMore().
          }
    }

    public void doThat() {
       synchronized(lock1) {
              // code here is guaranteed not to be executes at the same time
              // as the synchronized code in doThis().
              // doMore() may execute concurrently
        }
    }

    public void doMore() {
       synchronized(lock2) {
              // code here is guaranteed not to be executes at the same time
              // as the synchronized code in doThis().
              // doThat() may execute concurrently
        }
    }
}
Себастьян Томшке
источник
1

Я предполагаю, что этот вопрос о разнице между Thread Safe Singleton и Lazy инициализацией с двойной проверкой блокировки . Я всегда ссылаюсь на эту статью, когда мне нужно реализовать какой-то конкретный синглтон.

Ну, это потокобезопасный синглтон :

// Java program to create Thread Safe 
// Singleton class 
public class GFG  
{ 
  // private instance, so that it can be 
  // accessed by only by getInstance() method 
  private static GFG instance; 

  private GFG()  
  { 
    // private constructor 
  } 

 //synchronized method to control simultaneous access 
  synchronized public static GFG getInstance()  
  { 
    if (instance == null)  
    { 
      // if instance is null, initialize 
      instance = new GFG(); 
    } 
    return instance; 
  } 
} 

Плюсы:

  1. Ленивая инициализация возможна.

  2. Это потокобезопасно.

Минусы:

  1. Метод getInstance () синхронизирован, поэтому он снижает производительность, так как несколько потоков не могут получить к нему доступ одновременно.

Это Ленивая инициализация с двойной проверкой блокировки :

// Java code to explain double check locking 
public class GFG  
{ 
  // private instance, so that it can be 
  // accessed by only by getInstance() method 
  private static GFG instance; 

  private GFG()  
  { 
    // private constructor 
  } 

  public static GFG getInstance() 
  { 
    if (instance == null)  
    { 
      //synchronized block to remove overhead 
      synchronized (GFG.class) 
      { 
        if(instance==null) 
        { 
          // if instance is null, initialize 
          instance = new GFG(); 
        } 

      } 
    } 
    return instance; 
  } 
} 

Плюсы:

  1. Ленивая инициализация возможна.

  2. Это также потокобезопасный.

  3. Производительность снижена из-за синхронизации синхронизированного ключевого слова.

Минусы:

  1. В первый раз это может повлиять на производительность.

  2. Как минусы. Метод двойной проверки блокировки сносен, поэтому его можно использовать для высокопроизводительных многопоточных приложений.

Пожалуйста, обратитесь к этой статье для более подробной информации:

https://www.geeksforgeeks.org/java-singleton-design-pattern-practices-examples/

ofundefined
источник
-3

Синхронизация с потоками. 1) НИКОГДА не используйте синхронизированный (это) в потоке, он не работает. Синхронизация с (this) использует текущий поток в качестве объекта блокировки потока. Поскольку каждый поток не зависит от других потоков, координация синхронизации отсутствует. 2) Тесты кода показывают, что в Java 1.6 на Mac не работает синхронизация методов. 3) synchronized (lockObj), где lockObj - это общий общий объект всех синхронизирующихся на нем потоков. 4) ReenterantLock.lock () и .unlock () работают. Смотрите Java уроки для этого.

Следующий код показывает эти пункты. Он также содержит потокобезопасный вектор, который будет заменен на ArrayList, чтобы показать, что многие потоки, добавляемые в вектор, не теряют никакой информации, в то время как тот же самый с ArrayList может потерять информацию. 0) Текущий код показывает потерю информации из-за состояния гонки. A) Прокомментируйте текущую помеченную строку A и раскомментируйте строку A над ней, затем запустите, метод теряет данные, но это не должно. B) Обратный шаг A, раскомментируйте B и // конец блока}. Затем запустите, чтобы увидеть результаты без потери данных. C) Закомментируйте B, раскомментируйте C. Выполните, посмотрите, как синхронизируется (это) потеря данных, как и ожидалось. Не успеваю завершить все варианты, надеюсь, это поможет. Если выполняется синхронизация по (этому) или метод синхронизации работает, укажите, какую версию Java и ОС вы тестировали. Спасибо.

import java.util.*;

/** RaceCondition - Shows that when multiple threads compete for resources 
     thread one may grab the resource expecting to update a particular 
     area but is removed from the CPU before finishing.  Thread one still 
     points to that resource.  Then thread two grabs that resource and 
     completes the update.  Then thread one gets to complete the update, 
     which over writes thread two's work.
     DEMO:  1) Run as is - see missing counts from race condition, Run severa times, values change  
            2) Uncomment "synchronized(countLock){ }" - see counts work
            Synchronized creates a lock on that block of code, no other threads can 
            execute code within a block that another thread has a lock.
        3) Comment ArrayList, unComment Vector - See no loss in collection
            Vectors work like ArrayList, but Vectors are "Thread Safe"
         May use this code as long as attribution to the author remains intact.
     /mf
*/ 

public class RaceCondition {
    private ArrayList<Integer> raceList = new ArrayList<Integer>(); // simple add(#)
//  private Vector<Integer> raceList = new Vector<Integer>(); // simple add(#)

    private String countLock="lock";    // Object use for locking the raceCount
    private int raceCount = 0;        // simple add 1 to this counter
    private int MAX = 10000;        // Do this 10,000 times
    private int NUM_THREADS = 100;    // Create 100 threads

    public static void main(String [] args) {
    new RaceCondition();
    }

    public RaceCondition() {
    ArrayList<Thread> arT = new ArrayList<Thread>();

    // Create thread objects, add them to an array list
    for( int i=0; i<NUM_THREADS; i++){
        Thread rt = new RaceThread( ); // i );
        arT.add( rt );
    }

    // Start all object at once.
    for( Thread rt : arT ){
        rt.start();
    }

    // Wait for all threads to finish before we can print totals created by threads
    for( int i=0; i<NUM_THREADS; i++){
        try { arT.get(i).join(); }
        catch( InterruptedException ie ) { System.out.println("Interrupted thread "+i); }
    }

    // All threads finished, print the summary information.
    // (Try to print this informaiton without the join loop above)
    System.out.printf("\nRace condition, should have %,d. Really have %,d in array, and count of %,d.\n",
                MAX*NUM_THREADS, raceList.size(), raceCount );
    System.out.printf("Array lost %,d. Count lost %,d\n",
             MAX*NUM_THREADS-raceList.size(), MAX*NUM_THREADS-raceCount );
    }   // end RaceCondition constructor



    class RaceThread extends Thread {
    public void run() {
        for ( int i=0; i<MAX; i++){
        try {
            update( i );        
        }    // These  catches show when one thread steps on another's values
        catch( ArrayIndexOutOfBoundsException ai ){ System.out.print("A"); }
        catch( OutOfMemoryError oome ) { System.out.print("O"); }
        }
    }

    // so we don't lose counts, need to synchronize on some object, not primitive
    // Created "countLock" to show how this can work.
    // Comment out the synchronized and ending {, see that we lose counts.

//    public synchronized void update(int i){   // use A
    public void update(int i){                  // remove this when adding A
//      synchronized(countLock){            // or B
//      synchronized(this){             // or C
        raceCount = raceCount + 1;
        raceList.add( i );      // use Vector  
//          }           // end block for B or C
    }   // end update

    }   // end RaceThread inner class


} // end RaceCondition outter class
user2277816
источник
1
Синхронизация с «(это)» делает работу, и не не «использовать текущий поток в качестве синхронизирующего объекта», если текущий объект не имеет класса , который расширяет тему. -1
Маркиз Лорн