Чи є перевага використовувати синхронізований метод замість синхронізованого блоку?


401

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




1
@cletus це питання повністю відрізняється від stackoverflow.com/questions/442564/…
Yukio Fukuzawa

Відповіді:


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

vs.

// 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
}

44
Користь для споживача API полягає в тому, що за допомогою синхронізованого ключового слова в декларації методу також явно заявляється, що метод синхронізується на об’єкті об'єкта і є (імовірно) безпечним для потоків.
Скрубі

59
Я знаю, що це давнє питання, але синхронізація "цього" в деяких колах розглядається як анти-модель. Навмисний наслідок полягає в тому, що поза класом хтось може заблокувати посилання на об'єкт, рівний "цьому", і запобігти проходженню інших потоків через бар'єри в класі, що потенційно може створити ситуацію з тупиком. Створення "приватного кінцевого об'єкта = новий об'єкт ();" змінна суто для цілей блокування - це часто використовуване рішення. Ось ще одне питання, що стосується безпосередньо цього питання.
Justin.hughey

30
"тоді як синхронізація методу заблокувала б повний клас." Це неправильно. Він блокує не весь клас, а повний екземпляр. Кілька об'єктів одного класу містять весь власний замок. :) Вітаю
codepleb

4
Щось цікаве з цього приводу полягає в тому, що використання синхронізованого методу призведе до того, що згенерований байт-код матиме на 1 меншу інструкцію, оскільки методи мають синхронізований біт, записаний у свій підпис. Оскільки довжина байт-коду є фактором того, чи буде метод вбудований, переміщення блоку до підпису методу може бути різницею у рішенні. Теоретично все одно. Я б не базував дизайнерське рішення на одній збереженій байт-коді, це здається жахливою ідеєю. Але все - таки, це є різниця. =)
corsiKa

2
@corsiKa: ви зберігаєте більше однієї інструкції. synchronizedБлок здійснюється з допомогою двох команд, monitorenterі monitorexit, плюс обробник виключень , який гарантує , що monitorexitназивається навіть у винятковому випадку. Це все збережено при використанні synchronizedметоду.
Холгер

139

Єдина реальна відмінність полягає в тому, що синхронізований блок може вибрати, на якому об'єкті він синхронізується. Синхронізований метод може використовувати лише 'this'(або відповідний екземпляр класу для методу синхронізованого класу). Наприклад, це семантично еквівалентні:

synchronized void foo() {
  ...
}

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

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


Останній також може мати користь, якщо не весь код у foo () потрібно синхронізувати.
Еван

1
Це правда, але не те, що "Воїн" запитав: "Перевага синхронізованого методу" немає.
OscarRyz

76

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

Плюси:

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

Мінуси:

  • Синхронізується з цим і таким чином дає можливість стороннім користувачам також синхронізуватися з ним.
  • Складніше переміщувати код за межами синхронізованого блоку.

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

Плюси:

  • Дозволяє використовувати приватну змінну для блокування та змушує замок залишатися всередині класу.
  • Синхронізовані блоки можна знайти шляхом пошуку посилань на змінну.

Мінуси:

  • Синтаксис є складнішим і тому ускладнює читання коду.

Особисто я вважаю за краще використовувати синхронізовані методи з класами, орієнтованими лише на те, що потребує синхронізації. Такий клас повинен бути якомога меншим, і тому слід легко переглядати синхронізацію. Іншим не потрібно дбати про синхронізацію.


Коли ви говорите «залишайтеся всередині класу», ви маєте на увазі «залишитись усередині об’єкта », чи я щось пропускаю?
OldPeculier

36

Основна відмінність полягає в тому, що якщо ви використовуєте синхронізований блок, ви можете заблокувати об'єкт, відмінний від цього що дозволяє бути набагато гнучкішим.

Припустимо, у вас є черга повідомлень та кілька виробників та споживачів повідомлень. Ми не хочемо, щоб виробники заважали один одному, але споживачі повинні мати можливість отримувати повідомлення, не чекаючи виробників. Отже, ми просто створюємо об’єкт

Object writeLock = new Object();

І відтепер кожен раз, коли продюсери хочуть додавати нове повідомлення, ми просто блокуємо це:

synchronized(writeLock){
  // do something
}

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


2
Ваш приклад обмежений неруйнівними читаннями. Якщо зчитування видалить повідомлення з черги, це не вдасться, якщо це буде зроблено в той час, коли виробник записує в чергу.
закінчення

30

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

Синхронізовані методи мають два ефекти.
По-перше, коли одна нитка виконує синхронізований метод для об'єкта, всі інші потоки, які викликають синхронізовані методи для того ж блоку об'єктів (призупинення виконання), поки перший потік не буде виконано з об'єктом.

По-друге, при виході синхронізованого методу він автоматично встановлює зв'язок, що відбувається раніше, з будь-яким наступним викликом синхронізованого методу для того ж об'єкта. Це гарантує, що зміни стану об'єкта є видимими для всіх потоків.

Зауважте, що конструктори не можуть бути синхронізовані - використання ключового слова синхронізованого з конструктором є синтаксичною помилкою. Синхронізувати конструктори не має сенсу, оскільки лише нитка, яка створює об'єкт, повинна мати доступ до нього під час його створення.

Синхронізована заява

На відміну від синхронізованих методів, синхронізовані оператори повинні вказувати об’єкт, що забезпечує внутрішнє блокування: Найчастіше я використовую це для синхронізації доступу до списку чи карти, але не хочу блокувати доступ до всіх методів об’єкта.

З: Внутрішні блокування та синхронізація Синхронізація побудована навколо внутрішнього об'єкта, відомого як внутрішній замок або блокування монітора. (Специфікація 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();
    }
}

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


10
+1 за те, що єдиний на даний момент зазначив, що конструктори не можуть бути синхронізовані . Тобто, у конструктора у вас дійсно є лише один варіант: Синхронізовані блоки.
ef2011

Я перевірив ваш код, як вказано, але C завжди 0, тоді -2024260031 і єдине, що змінює його хеш-код. Яку поведінку слід бачити?
Джастін Джонсон

Ви повинні були цитувати нижче статті, з яких надано вміст: docs.oracle.com/javase/tutorial/essential/concurrency/… та docs.oracle.com/javase/tutorial/essential/concurrency/…
Равіндра бабу,

29

Примітка: статичні синхронізовані методи та блоки працюють на об’єкті класу.

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 виконує синхронізований метод, виконавчий потік ідентифікує, що структура_інформації методу має встановлений прапор 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;
}

7

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


6

Синхронізовані методи можна перевірити за допомогою API відображення. Це може бути корисно для тестування деяких контрактів, таких як усі методи в моделі синхронізовані .

Наступний фрагмент друкує всі синхронізовані методи Hashtable:

for (Method m : Hashtable.class.getMethods()) {
        if (Modifier.isSynchronized(m.getModifiers())) {
            System.out.println(m);
        }
}

5

Важлива примітка щодо використання синхронізованого блоку: обережно, що ви використовуєте як об'єкт блокування!

Фрагмент коду від користувача2277816 вище ілюструє цю точку тим, що в якості об'єкта блокування використовується посилання на літеральний рядок. Зрозумійте, що рядкові літерали автоматично інтернуються в Java, і ви повинні почати бачити проблему: кожен фрагмент коду, який синхронізується на буквальному «блокуванні», має той самий замок! Це може легко призвести до тупикових ситуацій із абсолютно незв'язаними фрагментами коду.

Слід бути обережними не лише об'єкти String. Примітивні бокси також становлять небезпеку, оскільки автобоксинг та метод 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

Як правило, вони в основному ті ж, що не є явними щодо монітора об'єкта, який використовується проти неявного цього об'єкта. Одним із недоліків синхронізованих методів, які, на мою думку, іноді не помічають, є те, що при використанні посилання "це" для синхронізації ви залишаєте відкритим можливість блокування зовнішніх об'єктів на одному об'єкті. Це може бути дуже тонкою помилкою, якщо ви натрапите на неї. Синхронізація з внутрішнім явним Об'єктом чи іншим наявним полем може уникнути цієї проблеми, повністю інкапсулюючи синхронізацію.


2

Як вже було сказано тут, синхронізований блок може використовувати визначену користувачем змінну як об'єкт блокування, коли синхронізована функція використовує лише "це". І звичайно, ви можете маніпулювати з областями своєї функції, які повинні бути синхронізовані. Але всі кажуть, що немає різниці між синхронізованою функцією та блоком, який охоплює всю функцію, використовуючи "цей" як об'єкт блокування. Це неправда, різниця полягає в байтовому коді, який буде генеруватися в обох ситуаціях. У разі використання синхронізованого блоку слід виділити локальну змінну, яка має посилання на "це". І як результат, у нас буде трохи більший розмір для функції (не актуально, якщо у вас є лише декілька функцій).

Більш детальне пояснення різниці ви можете знайти тут: 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;
}
}

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


читаючи цю книгу в даний момент ... мені цікаво ... якби цей список був приватним, а не загальнодоступним, і лише був би метод putIfAbsent, синхронізований (це) було б достатньо правильно? проблема в тому, що список може бути змінений і поза цим ListHelper?
dtc

@dtc так, якщо список був приватним і не протікав ніде в класі, то цього було б достатньо, якщо ви позначили кожен інший метод у класі, який також змінює список як синхронізований. Однак, блокування всього методу замість просто List
можливого

що має сенс. велике спасибі за відповідь! tbh, я знайшов книгу досить корисною для розширення своїх знань та способів наближення до багатопотокової, але вона також внесла до мене цілий новий світ плутанини
dcc

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/


-3

Синхронізація з нитками. 1) НІКОЛИ не використовуйте синхронізовану (це) у потоці, яка не працює. Синхронізуючи з (це), використовується поточний потік як об'єкт блокуючої нитки. Оскільки кожен потік не залежить від інших потоків, немає координації синхронізації. 2) Тести коду показують, що в Java 1.6 на Mac синхронізація методу не працює. 3) синхронізований (lockObj), де lockObj є загальним спільним об'єктом всіх потоків, що синхронізуються на ньому, працюватиме. 4) ReenterantLock.lock () та .unlock () працюють. Дивіться підручники Java для цього.

Наступний код показує ці моменти. Він також містить безпечний для потоків вектор, який би був замінений на ArrayList, щоб показати, що багато потоків, доданих до вектора, не втрачають ніякої інформації, в той час як той самий з ArrayList може втратити інформацію. 0) Поточний код показує втрату інформації через перегони A) Прокоментуйте поточну мітку A і відменте рядок A над нею, потім запустіть, метод втрачає дані, але він не повинен. Б) Зворотний крок 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

1
Синхронізація з «(це)» робить роботу, і не НЕ «використовувати поточний потік в якості синхронизирующего об'єкта», якщо поточний об'єкт не має класу , який розширює тему. -1
Маркіз Лорнський
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.