如果我在同一个类上同步了两个方法,它们可以同时运行吗?


164

如果我在同一个类上同步了两个方法,那么它们可以同时在同一个对象上运行吗?例如:

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

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

我知道我不能methodA()在两个不同的线程中的同一对象上运行两次。在methodB()

但是我可以methodB()methodA()仍然运行时在其他线程上运行吗?(相同的对象)

Answers:


148

两种方法都锁定同一监视器。因此,您不能同时在不同线程上的同一对象上执行它们(两种方法之一将阻塞,直到另一种方法完成)。


1
我对此问题有一个补充。假设这两个方法都是静态的,现在使用Class调用methodA,而使用t1中的A.methodA()和t2中的obj.methodB()这样的对象调用methodB。现在会发生什么,它们会阻止吗????
2013年

2
@ amod0017:obj.methodB()是同义A.methodB()的时候methodB()static。因此,是的,它们将阻塞(在类的监视器上,而不在对象的监视器上)。
NPE

会尝试回到正题。:)
13年

@NPE因此,即使两个方法都是静态的,并且同一对象上的2个线程t1和t2尝试同时调用methodA()和methodB(),则仅将执行1个(例如t1)线程,另一个线程必须等待,直到t1释放锁?
sreeprasad 2015年

8
请记住,静态方法对.class对象使用锁定。所以如果你有class A {static synchronized void m() {} }。然后一个线程调用new A().m()它获取对new A()对象的锁定。如果然后另一个线程调用A.m()它,则进入“方法无问题”,因为它所寻找的是A.class对象上的锁,而没有线程拥有这种锁。因此,即使你声明的方法synchronized它actualy 访问由两个不同的线程在同一时间。因此:永远不要使用对象引用来调用静态方法
Alex Semeniuk

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上?这是一个类级别的锁定示例吗?
Nimrod

2
@Nimrod:它锁定在lockA和lockB对象上,而不是A的实例上。这里没有任何东西锁定在一个类上。类级别的锁定将意味着使用诸如static synchronizedsynchronized (A.class)
Nathan Hughes

这是Java教程的链接,解释了此处确切给出的答案。
阿尔贝托·德保拉

18

Java Thread 进入实例同步的java方法时将获取对象级别的锁定,而进入静态同步的java方法时将获取类级别的锁定

在您的情况下,方法(实例)属于同一类。因此,无论何时线程进入Java同步方法或阻塞,它都会获取一个锁(调用该方法的对象)。因此,在第一个方法完成并释放锁(在对象上)之前,无法在同一对象上同时调用其他方法。


如果我在该类的两个不同实例上有两个线程,则它们将能够同时执行两个方法,以便一个线程调用一个同步方法,另一个线程调用第二个同步方法。如果我的理解是正确的,那么我可以private final Object lock = new object();与sync一起使用,以仅允许一个线程执行两种方法中的任何一种吗?谢谢
Yug Singh '18 -10-22

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是所有对象的父类,因此即使线程在该类的不同实例上,一次也只能访问同步块内的代码。谢谢。
尤格·辛格

如果在类中定义“私有最终对象锁”并与之同步,则每个类实例将填充“具有锁”,因此,其行为与synced(this)相同。
Oleksandr_DJ

是的,Object是所有类的父级,但是在您的情况下,“ lock”实例是“每个拥有类的实例”,因此,它与“ this”具有相同的同步效果。
Oleksandr_DJ

7

来自oracle文档链接

使方法同步具有两个作用:

首先,不可能对同一对象的两次同步方法调用进行交织。当一个线程正在为对象执行同步方法时,所有其他为同一对象块调用同步方法的线程(挂起执行),直到第一个线程对该对象完成。

其次,当同步方法退出时,它会与随后对同一对象的同步方法的任何调用自动建立事前关联关系。这样可以保证对对象状态的更改对所有线程都是可见的

这将回答您的问题:在同一对象上,当执行第一个同步方法时,您不能调用第二个同步方法。

请查看此文档页面以了解固有锁和锁行为。


6

将您的代码视为以下代码:

class A {

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

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

因此,在方法级别同步仅表示已同步(此)。如果任何线程运行此类的方法,它将在开始执行之前获取该锁,并保持该锁直到该方法的执行完成。

但是,可以在methodA()仍在运行时在不同的线程上运行methodB()吗?(相同的对象)

确实,这是不可能的!

因此,多个线程将无法同时在同一对象上运行任何数量的同步方法。


如果我在同一类的两个不同对象上创建线程该怎么办?在这种情况下,如果我从一个线程中调用一个方法而从第二个线程中调用另一个方法,它们将不会同时执行?
Yug Singh,

2
因为它们是不同的对象,所以它们会。也就是说,如果要防止这种情况发生,则可以使用静态方法并同步类,或者将类变量对象用作锁,或者使类成为Singleton。@Yug Singh
Khosro 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 in> methodA Out .. methodB in> methodB Out 带有* noEffectOfSynchronizedAsMethodsCalledOnDifferentObjects()*的输出已评论

和Ouput,其中带有syncedEffectiveAsMethodsCalledOnSameObject ()的 评论为注释 -输出在突出显示的部分中显示了Thread1和Thread0同时访问methodA-

带有* synchronizedEffectiveAsMethodsCalledOnSameObject()*的输出已注释

增加线程数将使其更加引人注目。


2

不,这是不可能的,如果可能的话,两种方法都可能同时更新同一变量,这很容易破坏数据。


2

是的,它们可以同时在两个线程中运行。如果创建该类的2个对象,因为每个对象仅包含一个锁,并且每个同步方法都需要锁。因此,如果要同时运行,请创建两个对象,然后尝试使用这些对象引用来运行。


1

您正在对象上而不是类上同步它。因此它们不能在同一对象上同时运行


0

两个不同的线程在单个对象上执行通用的同步方法,因为该对象相同,所以当一个线程将其与同步方法一起使用时,它将不得不更改该锁,如果启用了锁,则该线程将进入等待状态,如果禁用了锁,则它可以访问该对象,而访问该对象将启用该锁,并且仅在执行完成时才释放该锁。当其他线程到达时,它将更改该锁,因为启用该锁,它将一直等到第一个线程完成其执行并释放施加在对象上的锁之后,一旦释放该锁,第二个线程将可以访问该对象,并且它将启用锁定,直到执行为止。因此执行不会是非并发的,两个线程将一一执行,


1
请正确标点并大写该烂摊子。没有“ varify”这样的词。
罗恩侯爵
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.