我应该尝试避免静态同步方法

时间:2016-04-21 11:51:54

标签: java

根据我的理解,以下代码效率不高:

class Foo {
    static Resource resource1;
    static Resource resource2;

    static synchronized void methodA() {
       resource1.add("abc");
    }

    static synchronized void methodB() {
       resource2.add("abc");
    }
}

根据我的理解,这两种方法都锁定在一个对象(类对象Foo.class)中,所以我猜测以下是一个很好的优化?

class Foo {
    static Resource resource1;
    static Resource resource2;

    static void methodA()  {
       synchronized(resource1) {
           resource1.add("abc");
       }
    }

    static void methodB()  {
       synchronized(resource2) {
           resource2.add("123");
       }
    }
}

只要这两种资源不相互依赖。

我应该何时考虑使用静态同步方法?

4 个答案:

答案 0 :(得分:7)

当您的类提取对单个critical resource的访问权时,请使用static synchronized构造,因此锁定该类在语义上是正确的。

如果您的类抽象访问多个关键资源,那么您必须使用更精细的锁定,如您的示例所示。

您可以将方法的synchronized修饰符视为语法糖,除了锁定类之外没有额外的黑魔法(如果方法不是静态的,则没有实例)。

在您的第一个示例中,如果单个类提供对两个不同关键资源的访问,如果它们完全不相关,那么这是有疑问的。也许您可以将关键部分移动到资源类本身。

答案 1 :(得分:5)

您的优化是正确的。

Foo.class

上的第一个代码锁定

第二个代码锁定了两个不同的对象:resource1resource2

视觉上你可以想象这个

第一个代码:

 Thread 1              Thread 2
 ------------------------------

 Foo.methodA()

                        Foo.methodB()   
 // A call to methodB needs to wait for completion of methodA

第二段代码:

 Thread 1              Thread 2
 ------------------------------

 Foo.methodA()         Foo.methodB()    
 // At the same time in a machine with at least a dual core

只有在有一个资源要同步时,才应考虑使用静态同步方法。

答案 2 :(得分:0)

优化很好,但要注意可能的死锁。例如,有时您决定访问这两种资源:

class Foo {
    static Resource resource1;
    static Resource resource2;

    static void methodA()  {
       synchronized(resource1) {
           resource1.add("abc");
           synchronized(resource2) {
               resource2.add("abc");
           }
       }
    }

    static void methodB()  {
       synchronized(resource2) {
           resource2.add("123");
           synchronized(resource1) {
               resource1.add("123");
           }
       }
    }
}

这可能导致死锁:

  1. 线程A尝试执行methodA();
  2. 线程B尝试同时执行methodB();
  3. 线程A获取resource1锁定,线程B获取resource2 lock
  4. 线程A尝试获取resource2锁定,并开始等待锁定释放
  5. 线程B尝试获取resource1锁定,并开始等待锁定释放
  6. 死锁
  7. 为避免这种情况,您可以使Resource类成为线程安全的:

    class Resource {
        private final Object mLock = new Object();
        ...
        public void add(String str) {
            synchronized(mLock) {
                //do stuff
            }
        }
    }
    

答案 3 :(得分:0)

第二种方法(锁定对象)是首选,因为它可以让您更好地控制何时应该进行锁定。更重要的是,它可以防止你的类的外部一方无限期地锁定你的类,从而阻止你自己的方法执行。

请考虑以下内容:想象一下,一些外部代码包含以下语句:

 synchronized (Foo.class) {
     Thread.sleep(10000);
 }

现在,如果你在方法1中使用了类方法本身的synchronized,那么同时尝试调用methodA或methodB的其他类将被阻塞,直到睡眠完成。但是,如果您在方法2中使用内部对象的内部锁定,那么其他类将不必等待。

由于上述原因,我不会真的推荐方法1。

PS我刚注意到方法2中的内部锁未被声明为final。如果在方法忙于锁定它们时重新分配它们将是一个问题(然后锁将在不同的实例上)。为了防止这种情况,请按如下方式声明最终:

final static Resource resource1 = new Resource(...);
final static Resource resource2 = new Resource(...);

简而言之,永远不要在非最终对象上同步。