【问题标题】:Can more than 1 threads execute a static synchronized method in JAVAJAVA中可以多于1个线程执行一个静态同步方法吗
【发布时间】:2015-02-25 22:05:04
【问题描述】:

我一直在网上寻找有关如何允许执行静态同步方法的信息。我发现静态同步方法将获取类锁。据我了解,这可确保仅允许所有现有类实例之一执行静态同步方法。真的是这样吗?是否可以有 2 个类实例同时执行静态同步方法?因此,为了使其更直观,我添加了一个代码示例。

public class A {

   private static synchronized void m1() {
      //Print something
   }

   private synchronized void m2() {
      //Print something else
   }
}

我明白,因为静态方法是获取类级监视器,非静态方法是获取对象级监视器,所以两者可以同时从两个不同的线程执行,如下所示:

A a = new A;
a.m2();//object-level lock acquired
a.m1();//Class-level lock acquired

但是,如果我们有 3 个上述类的实例,它们可以同时运行 m1() 吗?我认为他们不能,但我不确定。那么这会发生吗?

A a = new A;
A aa = new A;
A aaa = new A;

a.m1();
aa.m1();
aaa.m1();

【问题讨论】:

  • 您最好将静态 m1 方法调用写为“A.m1()”,而不是“a.m1()”等 - 在最后一个示例中,您不需要 a, aa, aaa 对象。

标签: java multithreading


【解决方案1】:

据我了解,这确保了所有现有类实例中只有一个被允许执行静态同步方法。

不,这意味着一个线程将能够执行该方法,并且同步锁是特定类加载器中的Class对象。

正如the specification 所说:

同步方法在执行之前获取监视器 (§17.1)。

对于类(静态)方法,使用与该方法的类的 Class 对象关联的监视器。


是否可以有 2 个类实例同时执行静态同步方法?

“类实例”是一个模糊的术语。如果您的意思是类的实例,那是无关紧要的;这是一个static(类范围)方法,而不是实例方法。如果您的意思是 Class objects,那么这就是我在上面提到类加载器的原因:在不寻常的情况下,您拥有多个类加载器,您可以拥有多个 Class 对象— 例如,类的副本(不是它的 instances)。如果您有多个 Class 对象,则每个对象都有自己的 static 方法副本。因此,由于在不同对象上同步了两个副本,一个线程可以调用其中一个的 synchronized static 方法,而另一个线程正在调用 不同 其中之一。

但这是一个极端情况。在正常情况下,这不是问题:您只有一个方法副本,并且它在 Class 对象的一个​​副本上同步,并且在任何给定时间只有一个线程可以执行它。

【讨论】:

  • 重要的是类加载器部分。
  • 这里没有什么是我不明白的。我没有得到的是允许执行静态同步方法的 2 个或更多线程。我非常了解非静态同步方法将获取对象监视器,而静态方法将获取类监视器。你没有回答我的问题。
  • @RumenHristov: “我不明白是否有 2 个或更多线程可以执行静态同步方法” 不,这就是为什么我说“一个线程”(强调)上面。
  • 好的。现在你能详细说明一下吗,因为我已经想到是这种情况,但不知道为什么。
  • 好的。出色的。我对答案很满意。但是,我们可以通过许多类对象来拥有 A.m1() 吗?这与从类实例内部调用 m1() 不同。
【解决方案2】:

除了其他答案之外,还要澄清一点:

一般来说,没有两个线程可以访问在类监视器上同步的静态方法。

但这与类本身的实例没有任何关系。您不需要拥有该类的实例即可让线程运行该静态方法。该方法是静态的,因此即使没有类的实例也可以使用。也就是说,内存中不存在A,但你可以访问它的方法。

例如,如果您的方法是公开的,您可以执行以下操作:

public class Test extends Thread {
    @Override
    public void run() {
        A.m1();
    }

    public static void main( String args[] ) {
       Test thread1 = new Test();
       Test thread2 = new Test();

       thread1.start();
       thread2.start();
    }
}

运行这个Test 类,您将有两个线程,它们会调用A.m1(),并且由于它在类监视器上同步,它们将无法同时执行此操作。但这里的重点是内存中甚至没有一个 A 类型的对象。他们直接使用静态方法,而不创建实例。不需要A 的实例来运行A.m1() 方法!

现在,您将方法设为私有。所以你可能会想,除非你有A 的实例,否则没有办法使用它。但事实并非如此。您只能在同一个类的主体中使用它,但您仍然可以在 A 中编写一个能够创建两个线程的 static 方法(例如,扩展 Thread 的静态嵌套类),这两个线程将运行A.m1(),而不会创建A 的单个实例。

这就是为什么我们坚持该规则适用于线程而不是A的实例。线程数与实例数无关。


对于类对象:类对象(如A.class)属于Class 类型,在Java 中用于从上一级表示类及其能力。您可以使用它来创建类的实例,而无需使用单词 new、调用其方法以及许多其他用途。

所以,A 有这样一个类对象,而该对象就是它的静态方法监视器。加载该类时会创建一个类对象。该类通常在程序需要时加载。

但在某些情况下,如果程序中有多个类加载器,理论上可能会发生这样的情况,其中两个加载了A.class 文件(或从网上下载,或从其他来源获取,没关系 - 只是它们都采用相同的字节码,具有相同的名称和包,并加载它)。

在这种情况下,您可能有两个具有相同名称的类,但 Java 不会将它们视为同一个类。如果你得到一个实例(相当于new A()),然后是另一个实例,它或多或少就像拥有两个具有相同代码的不同类。在这种情况下,由于就 Java 而言,尽管名称相同,但它们不是同一个类,那么您可以让一个线程运行“A #1”的方法 m1,而一个线程运行 @987654341 @ of "A #2",他们可以同时这样做。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2012-11-15
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多