Java - 同步静态方法

12

这是我在此链接上找到的一段文字。

避免锁定静态方法

最糟糕的解决方案是在静态方法上放置“synchronized”关键字,这意味着它将锁定该类的所有实例。

为什么同步静态方法会锁定类的所有实例?它不应该只锁定Class吗?


7
由于静态方法可供所有实例使用,它会锁定所有实例。 - duffymo
虽然静态方法可以被实例访问,但它们不应该通过实例来访问。它们只能以静态方式访问。因此,作者的说法仍然不正确。 - sotn
1
@duffymo 不,它不会锁定任何实例。'synchronized(this)'将在此锁定期间进行。 - user207421
4
断章取义的危险性。阅读整篇文章后,很明显作者谈论的是当多个线程尝试访问同一个静态同步方法时会发生什么。观点不是对象实例,而是线程 - Perception
@duffymo 一种同步方法在执行前会获取一个监视器(§17.1)。对于类(静态)方法,使用的是与方法的相关联的 Class 对象的监视器。对于实例方法,使用的是与this(调用方法的对象)相关联的监视器。 - Robert Christian
@RobertChristian。太好了。值得三年的等待。 - duffymo
6个回答

35

要理解这一点,最简单的方法是比较锁对实例方法和静态方法的工作方式。假设你有一个Test.java类,其中包含以下两个方法。

public class Test{
   public synchronized void instanceMethod(){
   }

   public synchronized static void staticMethod(){
   } 
}

同时,有两个Test类的实例testA和testB,还有两个线程tA和tB并行尝试访问Test类。

在instanceMethod上加锁: 当tA获取testA的instanceMethod的锁时,tB无法访问testA中相同的方法。然而,tB仍然可以自由地调用testB中的instanceMethod,因为对instanceMethod进行同步是基于实例级别的锁定。

在staticMethod上加锁: 然而,当tA获取staticMethod的锁时,锁与testA或testB无关,因为对静态方法的同步是基于类级别的锁定。这意味着直到tA释放锁之前,tB无法访问staticMethod。


锁定机制的差异解释得非常好。 - asgs
1
最后终于明白了静态同步方法的概念。解释得很好。 - sonia
是的,这是关于锁定实例与静态方法的一个非常不错的概述。 - JohnMerlino
我不确定我喜欢这个解释。你说“tA获取了testA的instanceMethod上的锁”,这在我看来是不正确的。应该是tA获取了实例testA上的锁,而不是某个实例方法上的锁。 - searchengine27
似乎这两种方法是相同的,所以我可能会编辑最后一段的最后一句话。我会写:“这意味着在tA释放锁之前,tB无法访问任何方法”。 - Aerox
请问您能否展示整个代码? - PAA

8

实际上,对于类Foo的静态方法进行锁定,与对Foo.class(唯一实例)进行锁定相同:

public static void doSomething()
{
    synchronized(Foo.class)
    {
        // ...
    }
}

7

这是我的测试代码,证明您是对的,而文章有点过于谨慎:

class Y {
    static synchronized void staticSleep() {
        System.out.println("Start static sleep");
        try {
            Thread.sleep(2000);
        } catch (InterruptedException e) {
        }
        System.out.println("End static sleep");
    }

    synchronized void instanceSleep() {
        System.out.println("Start instance sleep");
        try {
            Thread.sleep(200);
        } catch (InterruptedException e) {
        }
        System.out.println("End instance sleep");
    }
}

public class X {
    public static void main(String[] args) {
        for (int i = 0; i < 2; ++i) {
            new Thread(new Runnable() {

                public void run() {
                    Y.staticSleep();
                }
            }).start();
        }

        for (int i = 0; i < 10; ++i) {
            new Thread(new Runnable() {

                public void run() {
                    new Y().instanceSleep();
                }
            }).start();
        }
    }
}

输出:

Start instance sleep
Start instance sleep
Start instance sleep
Start instance sleep
Start instance sleep
Start static sleep
Start instance sleep
Start instance sleep
Start instance sleep
Start instance sleep
Start instance sleep
End instance sleep
End instance sleep
End instance sleep
End instance sleep
End instance sleep
End instance sleep
End instance sleep
End instance sleep
End instance sleep
End instance sleep
End static sleep
Start static sleep
End static sleep

因此,static synchronized 与实例的 synchronized 方法无关...

当然,如果在整个系统中广泛使用 static synchronised 方法,则可以预期它们将对多线程系统的吞吐量产生最大影响,因此要谨慎使用...


2
感谢提供测试代码!即使过于谨慎也不能证明作者的观点是正确的。实例锁和类锁是分开的。这篇文章让我对备受尊崇的SCJP产生了怀疑,特别是因为作者从他的简历上看是一位专家。 - sotn

4
你是正确的——实际上锁定的是 Class 实例本身,而不是类的任何实例(更不用说所有实例)——但我认为你过于字面理解所链接的页面了。它本身使用了短语“静态锁(Class锁)”,因此其作者显然知道锁定的工作原理。但如果你有许多不同线程中的实例都在使用同步静态方法,则这些实例将相互阻塞。同步的静态方法不会导致同步的 实例 方法的阻塞,但问题仍然存在。

2

它并没有说“锁定类的所有实例”,而是说“在类的所有实例上锁定”。这种措辞很糟糕,实际上是不正确的,但它并没有像你所说的那样。


2

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