Чому синхронізований блок краще, ніж синхронізований метод?


75

Я почав вчитися синхронізації потоків.

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

public class Counter {

   private static int count = 0;

   public static synchronized int getCount() {
      return count;
   }

   public synchronized setCount(int count) {
      this.count = count;
   }

}

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

public class Singleton {

   private static volatile Singleton _instance;

   public static Singleton getInstance() {
      if (_instance == null) {
         synchronized(Singleton.class) {
            if (_instance == null)
               _instance = new Singleton();
         }
      }
      return _instance;
   }
}

Коли слід використовувати synchronizedметод і synchronizedблокувати?

Чому synchronizedблок краще synchronizedметоду?


1
Ви також повинні прочитати en.wikipedia.org/wiki/Double- Check_locking
Рене Посилання

1
У першому випадку я б використовував AtomicInteger, а у другому - enumдля Singleton.
Пітер Лорі

Де сказано, що краще? Що є підставою для вашого запитання?
user207421 02

Відповіді:


89

Справа не в кращому, просто в іншому.

При синхронізації методу ви ефективно синхронізуєтесь із самим об’єктом. У випадку статичного методу ви синхронізуєтесь із класом об'єкта. Отже, наступні дві частини коду виконуються однаково:

public synchronized int getCount() {
    // ...
}

Це так само, як ви це написали.

public int getCount() {
    synchronized (this) {
        // ...
    }
}

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


Так, це стосується змінних та методів класу, за винятком того, що отримується монітор для відповідного об'єкта Класу, а не екземпляра (this).
Aniket Thakur

7
Так, це інакше, але є дуже вагомі причини, чому ви ніколи не хочете виконувати синхронізацію, thisтому ця відповідь трохи не відповідає суті.
Voo

4
@Voo: Я категорично не згоден з тим, що ви ніколи не захочете синхронізувати this. Насправді це вигода, коли ви хочете викликати купу синхронізованих методів послідовно. Ви можете синхронізувати об'єкт протягом усього часу, і вам не доведеться турбуватися про вивільнення та повторне отримання блокувань для кожного виклику методу. Існує безліч різних шаблонів, які можна використовувати для синхронізації, і кожен має свої плюси і мінуси залежно від ситуації.
Ерік Робертсон,

2
Існує абсолютно ніякої різниці між синхронізованим методом і методом, єдиним блоком верхнього рівня якого synchronized(someObj), насправді компілятор генерує код для синхронізованого як би someObj==this. Отже, немає переваги робити це, але ви виставляєте внутрішні деталі зовнішньому світу, що явно порушує інкапсуляцію. Ну, є одна перевага: Ви економите близько 20 байт.
Voo

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

45

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

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

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

private final Object lockObject = new Object();

public void getCount() {
    synchronized( lockObject ) {
        ...
    }
}

Цей прийом згадується в Ефективній Java Блоха (2-е видання), пункт # 70


2
Навіть не потрібно бути зловмисним, дуже легко і безневинно шукати об’єкт, який ви отримаєте звідкись, щоб заблокувати (скажімо, тому, що ви хочете синхронізувати доступ саме до цього об’єкта). Краще уникати таких проблем
Voo

7
@wolfcastle: Не могли б ви пояснити, що таке "зловмисний користувач вашого коду"? Як вони зловмисні, псуючи власну заявку?
Ерік Робертсон,

11
@ErickRobertson Image Ви надаєте якусь послугу із загальнодоступним API. Якщо ваш API виставляє якийсь змінний об'єкт, робота якого залежить від блокування цього об'єкта, один зловмисний клієнт може отримати об'єкт, заблокувати його, а потім зациклювати назавжди, утримуючи замок. Це може перешкодити іншим клієнтам мати можливість працювати належним чином. В основному це атака типу відмова в обслуговуванні. Тож вони не псують лише власну заявку.
wolfcastle

Хоча це стара відповідь, я маю коментар до цієї відповіді. "Зловмисний користувач не може отримати блокування вашого приватного об’єкта." - а якщо "зловмисний користувач" використовує інший об'єкт у synchronizedблоці, а не "ваш приватний об'єкт"?
arnobpl

37

Різниця полягає в тому, в який замок набувається:

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

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

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

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

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


Отже, якщо у мене є синхронізований блок і в ньому запущений потік, чи може інший потік зайти в об’єкт і запустити синхронізований блок де-небудь ще в об’єкті? Якщо у мене є синхронізований метод і в ньому виконується один потік, жоден інший потік не може виконуватися в цьому об’єкті або лише в синхронізованих областях об’єкта?
Зла пральна машина

6

Визначте „краще”. Синхронізований блок є лише кращим, оскільки він дозволяє:

  1. Синхронізуйте з іншим об’єктом
  2. Обмежте обсяг синхронізації

Тепер ваш конкретний приклад - це приклад підозрілої перевіреної схеми блокування, яка є підозрілою (у старих версіях Java вона була зламана, і це легко зробити неправильно).

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


5

Різниця між синхронізованим блоком та синхронізованим методом наступна:

  1. синхронізований блок зменшує область блокування, але область блокування синхронізованого методу - це цілий метод.
  2. синхронізований блок має кращу продуктивність, оскільки блокується лише критичний розділ, але синхронізований метод має низьку продуктивність, ніж блок.
  3. синхронізований блок забезпечує детальний контроль над блокуванням, але синхронізований метод блокування або на поточному об’єкті, представленому цим, або на рівні класу.
  4. синхронізований блок може кидати NullPointerException, але синхронізований метод не кидає.
  5. синхронізований блок: synchronized(this){}

    синхронізований метод: public synchronized void fun(){}


3

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

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

public class AClass {
private int x;
private final Object lock = new Object();     //it must be final!

 public void setX() {
    synchronized(lock) {
        x++;
    }
 }
}

1

У вашому випадку обидва еквівалентні!

Синхронізація статичного методу еквівалентна синхронізованому блоку на відповідному об’єкті Class.

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

public static synchronized int getCount() {
    // ...
}

це те саме, що

public int getCount() {
    synchronized (ClassName.class) {
        // ...
    }
}

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

Тоді це для кожного екземпляра або об’єкта, який ви створюєте. Два потоки можуть обробляти один і той же метод у двох різних Об’єктах, незалежних один від одного.
Aniket Thakur

1

Оскільки блокування дороге, коли ви використовуєте синхронізований блок, ви блокуєте лише якщо _instance == null, а після _instanceостаточної ініціалізації ви ніколи не заблокуєте. Але при синхронізації методу ви блокуєте безумовно, навіть після _instanceініціалізації. Це ідея подвійної перевірки шаблону оптимізації блокування http://en.wikipedia.org/wiki/Double- Check_Locking .


1

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

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

Весь метод може бути позначений як синхронізований, що призводить до неявного блокування цього посилання (методи екземпляра) або класу (статичні методи). Це дуже зручний механізм для досягнення синхронізації.

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

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

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

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


0

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

Синхронізований метод: В основному ці 2 методи синхронізації відключають багатопотоковість. Отже, один потік завершує метод1 (), а інший потік чекає завершення потоку1.

клас SyncExerciseWithSyncMethod {

public synchronized void method1() {
    try {
        System.out.println("In Method 1");
        Thread.sleep(5000);
    } catch (Exception e) {
        System.out.println("Catch of method 1");
    } finally {
        System.out.println("Finally of method 1");
    }

}

public synchronized void method2() {
    try {
        for (int i = 1; i < 10; i++) {
            System.out.println("Method 2 " + i);
            Thread.sleep(1000);
        }
    } catch (Exception e) {
        System.out.println("Catch of method 2");
    } finally {
        System.out.println("Finally of method 2");
    }
}

}

Вихідні дані

У способі 1

Нарешті, метод 1

Спосіб 2 1

Спосіб 2 2

Спосіб 2 3

Спосіб 2 4

Спосіб 2 5

Спосіб 2 6

Спосіб 2 7

Спосіб 2 8

Спосіб 2 9

Нарешті, метод 2


Синхронізований блок: дозволяє кільком потокам одночасно отримувати доступ до одного і того ж об’єкта [Увімкнено багатопотоковість].

клас SyncExerciseWithSyncBlock {

public Object lock1 = new Object();
public Object lock2 = new Object();

public void method1() {
    synchronized (lock1) {
        try {
            System.out.println("In Method 1");
            Thread.sleep(5000);
        } catch (Exception e) {
            System.out.println("Catch of method 1");
        } finally {
            System.out.println("Finally of method 1");
        }
    }

}

public void method2() {

    synchronized (lock2) {
        try {
            for (int i = 1; i < 10; i++) {
                System.out.println("Method 2 " + i);
                Thread.sleep(1000);
            }
        } catch (Exception e) {
            System.out.println("Catch of method 2");
        } finally {
            System.out.println("Finally of method 2");
        }
    }
}

}

Вихідні дані

У способі 1

Спосіб 2 1

Спосіб 2 2

Спосіб 2 3

Спосіб 2 4

Спосіб 2 5

Нарешті, метод 1

Спосіб 2 6

Спосіб 2 7

Спосіб 2 8

Спосіб 2 9

Нарешті, метод 2

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.