根据我的理解,以下代码效率不高:
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");
}
}
}
只要这两种资源不相互依赖。
我应该何时考虑使用静态同步方法?
答案 0 :(得分:7)
当您的类提取对单个critical resource的访问权时,请使用static synchronized
构造,因此锁定该类在语义上是正确的。
如果您的类抽象访问多个关键资源,那么您必须使用更精细的锁定,如您的示例所示。
您可以将方法的synchronized
修饰符视为语法糖,除了锁定类之外没有额外的黑魔法(如果方法不是静态的,则没有实例)。
在您的第一个示例中,如果单个类提供对两个不同关键资源的访问,如果它们完全不相关,那么这是有疑问的。也许您可以将关键部分移动到资源类本身。
答案 1 :(得分:5)
您的优化是正确的。
Foo.class
第二个代码锁定了两个不同的对象:resource1
和resource2
。
视觉上你可以想象这个
第一个代码:
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");
}
}
}
}
这可能导致死锁:
为避免这种情况,您可以使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(...);
简而言之,永远不要在非最终对象上同步。