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

221
如果我在同一个类上同步了两个方法,它们能够同时在同一对象上运行吗?例如:

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

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

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

我知道在两个不同的线程上无法在相同的对象上两次运行methodA(),同样的情况也适用于methodB()

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

12个回答

196

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


1
我对这个问题有一个补充。假设两种方法都是静态的,现在使用类调用methodA,而使用对象调用methodB,例如在t1中使用A.methodA(),在t2中使用obj.methodB()。现在会发生什么,它们会阻塞吗? - amod
2
@amod0017:当methodB()是静态的时,obj.methodB()A.methodB()是同义的。因此,是的,它们将会阻塞(在类的监视器上,而不是对象的)。 - NPE
会尝试并尽快回复。 :) - amod
@NPE 所以即使两个方法都是静态的,两个线程t1和t2在同一个对象上尝试同时调用methodA()和methodB(),那么只有1个线程(比如t1)会执行,另一个线程必须等待,直到t1释放锁定? - sreeprasad
11
请注意,静态方法在.class对象上使用锁。所以如果你有一个类A,有一个静态同步方法static synchronized void m() {}。然后一个线程调用了new A().m(),它会获得new A()对象上的锁。如果另一个线程调用A.m(),它可以__毫不费力地进入该方法__,因为它所寻找的是A.class对象上的锁,而此时__没有任何线程拥有这种类型的锁__。因此,即使你声明了方法是synchronized的,它实际上也会被__两个不同的线程同时访问__。因此:__永远不要使用对象引用来调用静态方法__。 - Alex Semeniuk
显示剩余2条评论

152
在示例中,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
3
@Nimrod:它正在锁定lockA和lockB对象,而不是A实例。这里没有锁定类。类级别的锁定意味着获取类对象上的锁,使用类似于static synchronizedsynchronized (A.class)的东西。 - Nathan Hughes
1
这里是Java教程的链接,详细解释了此处回答的内容:https://docs.oracle.com/javase/tutorial/essential/concurrency/locksync.html - Alberto de Paola

20

当Java线程进入一个实例同步的Java方法时,它会获得对象级别锁,并且当它进入静态同步的Java方法时,它会获得类级别锁。

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


如果我有两个线程在类的两个不同实例上,则它们将能够同时执行两种方法,使得一个线程调用一个同步方法,而另一个线程调用第二个同步方法。如果我的理解是正确的,那么我可以使用“private final Object lock = new object();”与synchronized来允许只有一个线程能够执行任一方法吗?谢谢。 - Yug Singh

15

在你的情况下,你对同一类实例上的两个方法进行了同步。因此,这两个方法不能在同一类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(),然后在两个方法中使用synchronized块来使用lock,那么你的陈述是否成立?在我看来,由于Object是所有对象的父类,所以即使线程在类的不同实例上运行,也只能同时有一个线程访问synchronized块内的代码。谢谢。 - Yug Singh
如果您在类中定义“private final Object lock”并使用它进行同步,那么每个类实例将拥有一个锁,因此它的行为与synchronized(this)相同。 - Oleksandr_DJ
是的,Object是所有类的父类,但在您的情况下,“lock”实例是“每个拥有类的实例”,因此它具有与同步“this”的相同效果。 - Oleksandr_DJ

12

将您的代码看作如下所示:

class A {

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

public void methodB() {
    synchronized(this){
      // method B body
    }
}
所以,方法级别的同步仅意味着同步 (this)。如果任何一个线程运行该类的一个方法,它将在开始执行之前获取锁并一直保持锁定状态,直到方法执行完成。 但是,如果我在同一对象上仍在运行方法A()的同时在不同的线程上运行methodB(),这是可能的吗? 实际上,这是不可能的!因此,多个线程将无法同时运行同一对象上的任何数量的同步方法。

如果我在同一类的两个不同对象上创建线程会怎样?在这种情况下,如果我从一个线程调用一个方法,从第二个线程调用另一个方法,它们不会同时执行吗? - Yug Singh
2
它们是不同的对象,所以它们会这样做。也就是说,如果你想要防止这种情况,你可以使用静态方法并同步类,或者使用一个类变量对象作为锁,或者将类设置为单例模式。@Yug Singh - Khosro Makari

8
从Oracle文档中链接可以得知,将方法设为同步方法有两个效果:

第一,同一个对象的同步方法无法交错执行。当一个线程正在执行该对象的同步方法时,所有其他线程调用该对象的同步方法都会被阻止(暂停执行),直到第一个线程完成对该对象的操作。

第二,在同步方法退出时,它会自动与该对象的任何后续同步方法调用建立happens-before关系。这保证了对对象状态的更改可被所有线程看到。

换言之,在同一个对象上,当第一个同步方法的执行仍在进行时,您不能调用第二个同步方法。
请查看此文档页面以了解固有锁和锁行为。

4
关键是同步的思想并不容易理解,只有在调用方法的相同对象实例上才会产生影响 - 这已经在答案和评论中强调过了 -
下面的示例程序旨在清晰地指出这一点 -
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();
    }
}
}

请注意,如果在不同的对象实例上调用方法,则允许同时访问的输出差异正如预期
没有效果的同步作为方法在不同对象上被调用时的输出已注释 - 输出按顺序methodA in > methodA Out .. methodB in > methodB Out Ouput with *noEffectOfSynchronizedAsMethodsCalledOnDifferentObjects()* commented synchronizedEffectiveAsMethodsCalledOnSameObject() 被注释的输出-输出显示了线程1和线程0在突出显示的部分同时访问methodA

Ouput with *synchronizedEffectiveAsMethodsCalledOnSameObject()* commented

增加线程数量会使其更加明显。


4

为了更清晰地说明,静态同步方法和非静态同步方法可能同时或并发运行,因为一个具有对象级别的锁,另一个具有类级别的锁。


2
你正在对对象进行同步,而不是对类进行同步。所以它们不能在同一个对象上同时运行。

2

不可能实现,如果可以实现,则两种方法可以同时更新相同的变量,这将很容易破坏数据。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接