在锁中调用调用序列,有两种情况

时间:2013-05-24 19:38:44

标签: java concurrency

我有一个带有synchronized方法的类:

class A {
    synchronized void method1() {};
    synchronized void method2() {};
}

如果某个客户端类想要调用method1(),那么method2()知道这些调用是原子的

第一种情况:

class B {    
    A a;    
    public void foo() {    
        synchronized(a) {
            a.method1();
            a.method2();
        }    
    }

}

第二种情况:

 class B {    
        A a;

        final Object lock = new Object();

        public void foo() {    
            synchronized(lock) {
                a.method1();
                a.method2();
            }    
        }

    }

我有什么理由可以使用一个案例而不是另一个案例吗?

2 个答案:

答案 0 :(得分:1)

在a上进行同步可能是最有意义的。这至少保持了“最少惊喜”的原则。 a是其他客户端同步的最自然对象。添加一些文档以使其更清晰。另外,您可以保证在A中的同步方法上获得锁定,如果您在A和B中使用不同的锁定,这可能比潜在的竞争锁定更有效。

答案 1 :(得分:0)

如果总是使用第一种技术调用方法,那么这两种方法实际上是原子的:一次只有一个线程(运行method1并运行method2),作为一个块。

在第二种情况下,B的任何给定实例将以原子方式调用这两个方法 - 但如果两个线程各自具有单独的B实例,那么method1()和{ {1}}来电可以交错。想象:

  • Thread1拥有method2()
  • Thread2拥有B b1 = new B(a)(对于同一个实例B b2 = new B(a)
  • Thread1调用a,获取对b1.newB()
  • 的锁定
  • Thread2调用b1.lock,获取b2.newB()上的锁定(由于它是b2.lock之外的单独对象,因此无法争用)
  • Thread1启动b1.lock
  • Thread2尝试启动a.method1()但由于已同步
  • 而被阻止
  • Thread1完成a.method1()
  • Thread2启动a.method1()
  • Thread1启动a.method1(),因为Thread2仍在运行a.method2()
    • 未达到原子性!

您可以通过使method1()成为静态最终版来解决这个问题,但这可能比您需要的锁定更多 - 现在B.lock的所有调用都是序列化的,而不是按{A.method1(); A.method2();}的实例序列化。

即使你使用第一次同步方法,你仍然受其他人玩得很好的摆布。如果其他人直接调用A(而不是通过a.method1()),您仍然可以进行非原子交错。

  • Thread1拥有B.foo
  • Thread2直接拥有B b1 = new B(a)a的相同实例)
  • Thread1调用a,获取对b1.newB()
  • 的锁定
  • Thread1启动b1.lock
  • Thread2尝试启动a.method1()但由于已同步
  • 而被阻止
  • Thread1完成a.method1()
  • Thread2启动a.method1()
  • Thread1启动a.method1(),因为Thread2仍在运行a.method2()
    • 未达到原子性!

除了限制method1()method1()的可见性之外,您无能为力。例如,您可以将它们打包为私有或受保护,并假设/希望有权访问这些方法的类比滥用它们的权力更好。如果你走那条路,那么你甚至不需要同步method2()method1() - 你可以记录并假设(如果你想的话,甚至是assert)他们会<{1}}被锁定时调用。