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


164

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

class A {
    public synchronized void methodA() {
        //method A
    }

    public synchronized void methodB() {
        // method B
    }
}

Я знаю, що я не можу methodA()двічі запускатись над одним об’єктом у двох різних потоках. те саме в methodB().

Але чи можна бігати methodB()по різній нитці, поки methodA()вона все ще працює? (той самий об’єкт)

Відповіді:


148

Обидва способи блокують один і той же монітор. Тому ви не можете одночасно виконувати їх на одному об'єкті з різних потоків (один з двох методів буде блокуватись, поки інший не буде закінчений).


1
У мене було доповнення до цього питання. Припустимо, що обидва способи є статичними, тепер методA називається за допомогою класу, а метод B викликається з використанням об'єкта типу A.methodA () в t1 і obj.methodB () в t2. Що буде зараз, чи заблокують ????
amod

2
@ amod0017: obj.methodB()є синонімом того, A.methodB()коли methodB()є static. Тому так, вони будуть блокувати (на моніторі класу, а не об'єкта).
NPE

спробує повернутися до цього. :)
amod

@NPE Отже, навіть якщо обидва способи є статичними, а 2 потоки t1 і t2 на одному об'єкті намагаються викликати methodA () і methodB () одночасно, тоді буде виконуватися лише 1 (скажімо, t1), а інший потік повинен почекати, поки t1 відпустить замок ?
sreeprasad

8
Майте на увазі, що статичні методи використовують замок на .classоб'єкті. Тож якщо є class A {static synchronized void m() {} }. І тоді одна нитка викликає new A().m()його, набуває замок на new A()об'єкті. Якщо тоді інший потік називає A.m()його ВВІЙТЕ МЕТОДУ НЕ ПРОБЛЕМУ, оскільки те, що він шукає, є блокуванням на A.classоб'єкті, а НІХ РОБОК не має такого типу блокування . Тож навіть якщо ви заявили про метод, synchronizedвін дійсно доступний двома різними потоками В ОДНІЙ ЧАС . Таким чином: ніколи не використовуйте посилання на об’єкти для виклику статичних методів
Олексій Семенюк,

113

У прикладі methodA і methodB є методами екземпляра (на відміну від статичних методів). Введення synchronizedметоду екземпляра означає, що потік повинен придбати замок ("внутрішній замок") на об'єктному екземплярі, до якого використовується метод, перш ніж потік може почати виконувати будь-який код у цьому методі.

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

Для того, щоб обидва способи працювали одночасно, їм довелося б використовувати різні блокування, наприклад:

class A {
    private final Object lockA = new Object();
    private final Object lockB = new Object();

    public void methodA() {
        synchronized(lockA) {
            //method A
        }
    }

    public void methodB() {
        synchronized(lockB) {
            //method B
        }
    }
}

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

Важливо розуміти, що хоча ми ставимо "синхронізоване" ключове слово за окремими методами, основна концепція - це внутрішній замок за лаштунками.

Ось як у підручнику Java описуються відносини:

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

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

Мета блокування - захист спільних даних. Ви б використовували окремі блокування, як показано в наведеному вище прикладі коду, лише якщо кожен замок захищав різні члени даних.


тож у цьому прикладі замок знаходиться на об'єктах lockA \ lockB, а не на класі A? Це приклад блокування рівня класу ?
Німрод

2
@Nimrod: він блокується на об’єктах lockA і на lockB, а не на екземплярі A. нічого не блокується у класі. Блокування на рівні класу означатиме отримання блокування на об’єкт класу, використовуючи щось на кшталт static synchronizedабоsynchronized (A.class)
Nathan Hughes

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

18

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

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


якщо у мене є два потоки на двох різних екземплярах класу, вони зможуть виконувати обидва методи одночасно, таким чином, що один потік викликає один синхронізований метод, а другий викликає другий синхронізований метод. Якщо моє розуміння є правильним, то чи можу я використовувати private final Object lock = new object();синхронізований, щоб дозволити виконати лише один потік будь-якого з методів? Спасибі
Юг Сінгх

13

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

class A {
    public synchronized void methodA() {
        //method A
    }
}

те саме, що:

class A {
    public void methodA() {
        synchronized(this){
            // code of method A
        }
    }
}

що робити, якщо я визначу блокування як private final Object lock = new Object();і тепер використовую lockсинхронізований блок двома методами, тоді ваше твердження буде істинним? IMO, оскільки Object є батьківським класом усіх об'єктів, тому навіть якщо потоки знаходяться на різних примірниках класу, лише один може отримати доступ до коду всередині синхронізованого блоку одночасно. Дякую.
Юг Сінгх

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

Так, Object є батьківським для всіх класів, але екземпляр "lock" у вашому випадку - це "екземпляр для власного класу", тому він має такий же ефект, як "this" для синхронізації.
Олександр_DJ

7

З посилання на документацію Oracle

Синхронізація методів має два ефекти:

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

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

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

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


6

Подумайте про свій код як наступний:

class A {

public void methodA() {
    synchronized(this){        
      //method A body
    }
}

public void methodB() {
    synchronized(this){
      // method B body
    }
}

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

Але чи можу я запустити methodB () на різних потоках, поки methodA () все ще працює? (той самий об’єкт)

Дійсно, це неможливо!

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


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

2
Вони будуть тому, що вони різні об’єкти. Тобто, якщо ви хочете запобігти цьому, ви можете використовувати статичні методи та синхронізувати клас або використовувати об’єкт змінної класу як замок або зробити клас Singleton. @Yug Singh
Хосров Makari

4

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


3

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

Нижче зразок програми - чітко визначити те саме -

public class Test {

public synchronized void methodA(String currentObjectName) throws InterruptedException {
    System.out.println(Thread.currentThread().getName() + "->" +currentObjectName + "->methodA in");
    Thread.sleep(1000);
    System.out.println(Thread.currentThread().getName() + "->" +currentObjectName + "->methodA out");
}

public synchronized void methodB(String currentObjectName)  throws InterruptedException {
    System.out.println(Thread.currentThread().getName() + "->" +currentObjectName + "->methodB in");
    Thread.sleep(1000);
    System.out.println(Thread.currentThread().getName() + "->" +currentObjectName + "->methodB out");
}

public static void main(String[] args){
    Test object1 = new Test();
    Test object2 = new Test();
    //passing object instances to the runnable to make calls later
    TestRunner runner = new TestRunner(object1,object2);
    // you need to start atleast two threads to properly see the behaviour
    Thread thread1 = new Thread(runner);
    thread1.start();
    Thread thread2 = new Thread(runner);
    thread2.start();
}
}

class TestRunner implements Runnable {
Test object1;
Test object2;

public TestRunner(Test h1,Test h2) {
    this.object1 = h1;
    this.object2 = h2;
}

@Override
public void run() {
    synchronizedEffectiveAsMethodsCalledOnSameObject(object1);
    //noEffectOfSynchronizedAsMethodsCalledOnDifferentObjects(object1,object2);
}

// this method calls the method A and B with same object instance object1 hence simultaneous NOT possible
private void synchronizedEffectiveAsMethodsCalledOnSameObject(Test object1) {
    try {
        object1.methodA("object1");
        object1.methodB("object1");
    } catch (InterruptedException e) {
        e.printStackTrace();
    }
}

// this method calls the method A and B with different object instances object1 and object2 hence simultaneous IS possible
private void noEffectOfSynchronizedAsMethodsCalledOnDifferentObjects(Test object1,Test object2) {
    try {
        object1.methodA("object1");
        object2.methodB("object2");
    } catch (InterruptedException e) {
        e.printStackTrace();
    }
}
}

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

Вихід з noEffectOfSynchronizedAsMethodsCalledOnDifferentObjects () прокоментував - вихід виходить у порядку methodA в> methodA Out .. methodB в> methodB Out Вихід з * noEffectOfSynchronizedAsMethodsCalledOnDifferentObjects () * прокоментував

та Ouput із синхронізованимEffectiveAsMethodsCalledOnSameObject () прокоментував - вихід показує одночасний доступ до methodA Thread1 та Thread0 у виділеному розділі -

Вихід із * синхронізованимEffectiveAsMethodsCalledOnSameObject () * прокоментував

Збільшення кількості ниток зробить це ще більш помітним.


2

Ні, це не можливо, якби це було можливим, тоді обидва способи могли б одночасно оновлювати одну і ту ж змінну, що може легко пошкодити дані.


2

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


1

Ви синхронізуєте його на об'єкті, а не на уроці. Тому вони не можуть одночасно працювати на одному об’єкті


0

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


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