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

164

Если я синхронизировал два метода в одном и том же классе, могут ли они одновременно работать на одном и том же объекте ? например:

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

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

Я знаю, что не могу methodA()дважды запустить один и тот же объект в двух разных потоках. то же самое в methodB().

Но могу ли я работать methodB()в другом потоке, пока methodA()он еще работает? (тот же объект)

Shelef
источник

Ответы:

148

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

NPE
источник
1
У меня было дополнение к этому вопросу. Предположим, что оба метода являются статическими, теперь methodA вызывается с использованием Class, а methodB вызывается с использованием таких объектов, как 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
        }
    }
}

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

Важно понимать, что, несмотря на то, что мы помещаем ключевое слово «synchronized» в отдельные методы, основная концепция - это внутренняя блокировка за кулисами.

Вот как учебник Java описывает отношения:

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

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

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

Натан Хьюз
источник
так что в этом примере блокировка находится на объектах lockA \ lockB, а не на классе A? Это пример блокировки на уровне класса ?
Нимрод
2
@Nimrod: он блокирует объекты lockA и lockB, а не экземпляр A. Ничто здесь не блокирует класс. блокировка на уровне класса будет означать получение блокировки на объект класса, используя что-то вроде static synchronizedилиsynchronized (A.class)
Натан Хьюз
Вот ссылка на Java-учебник, объясняющий, что именно ответ здесь.
Альберто де Паола
18

Поток Java получает блокировку уровня объекта, когда входит в экземпляр синхронизированного метода java, и получает блокировку уровня класса, когда входит в статический синхронизированный метод java.

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

Srikanth
источник
если у меня есть два потока в двух разных экземплярах класса, они смогут одновременно выполнять оба метода, так что один поток вызывает один синхронизированный метод, а другой - второй синхронизированный метод. Если мое понимание верно, то могу ли я использовать private final Object lock = new object();синхронизированный, чтобы позволить только одному потоку выполнять любой из методов? Спасибо
Юг Сингх
13

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

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

такой же как:

class A {
    public void methodA() {
        synchronized(this){
            // code of method A
        }
    }
}
Oleksandr_DJ
источник
Что если я определю блокировку как private final Object lock = new Object();и теперь использую lockс синхронизированным блоком в двух методах, то будет ли ваше утверждение верным? IMO, поскольку Object является родительским классом всех объектов, поэтому, даже если потоки находятся в разных экземплярах класса, только один может получить доступ к коду внутри синхронизированного блока за раз. Спасибо.
Юг Сингх
Если вы определяете «частную конечную блокировку объекта» в классе и синхронизируетесь с ним, вы заполняете «иметь блокировку для каждого экземпляра класса», поэтому он будет вести себя так же, как синхронизированный (это).
Oleksandr_DJ
Да, Object является родительским для всех классов, но экземпляр «lock» в вашем случае - это «экземпляр на класс-владелец», поэтому он имеет тот же эффект, что и «this» для синхронизации.
Oleksandr_DJ
7

Из документации оракула ссылка

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

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

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

Это ответит на ваш вопрос: для того же объекта вы не можете вызвать второй синхронизированный метод, когда выполняется выполнение первого синхронизированного метода.

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

Aditya W
источник
6

Думайте о своем коде как о следующем:

class A {

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

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

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

Но можно ли запустить methodB () в другом потоке, пока methodA () еще работает? (тот же объект)

Действительно, это невозможно!

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

Хосро Макари
источник
Что, если я создаю Threads на двух разных объектах одного и того же класса? В этом случае, если я вызову один метод из одного потока и другой метод из второго потока, они не будут выполняться одновременно?
Юг Сингх
2
Они будут, потому что они разные объекты. То есть, если вы хотите предотвратить это, вы можете использовать статические методы и синхронизировать класс или использовать объект переменной класса в качестве блокировки или создать класс Singleton. @ Юг Сингх
Хосро Макари
4

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

pacmanfordinner
источник
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();
    }
}
}

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

Ouput с noEffectOfSynchronizedAsMethodsCalledOnDifferentObjects () прокомментированных -The выход в порядке Methoda в> Methoda Out .. methodB в> methodB Out Выход с комментариями * noEffectOfSynchronizedAsMethodsCalledOnDifferentObjects () *

и Ouput с прокомментированным synchronizedEffectiveAsMethodsCalledOnSameObject () - выходные данные показывают одновременный доступ метода A Thread1 и Thread0 в выделенном разделе -

Выход с комментариями * synchronizedEffectiveAsMethodsCalledOnSameObject () *

Увеличение количества потоков сделает это еще более заметным.

somshivam
источник
2

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

fastcodejava
источник
2

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

coolts
источник
1

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

хуг
источник
0

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

Анкит Ядав
источник
1
Пожалуйста, сделайте акцент на этом беспорядке. Там нет такого слова, как «изменить».
Маркиз Лорн