静态同步方法和非同步静态方法的混淆

3

我有点困惑,请看下面的代码:

public class ThreadDemo {
  //non-static synchronized method
  synchronized void a(){
   actBusy();
  }

  //static synchronized method
  static synchronized void b(){
    actBusy();
  }

  //static method
  static void actBusy(){
    try{
      Thread.sleep(1000);
    }
    catch(InterruptedException e) {
      e.printStackTrace();
    }
  }

  public static void main(String[] args){
    final ThreadDemo x = new ThreadDemo();
    final ThreadDemo y = new ThreadDemo();
    Runnable runnable = new Runnable() {
      public void run() {
         int option = (int) (Math.random() * 4);
         switch (option){
           case 0: x.a();
             break;
           case 1: x.b();
             break;
           case 2: y.b();
             break;
           case 3: y.b();
             break;
         }
      }
    }   ;
    Thread t1 = new Thread(runnable);
    Thread t2 = new Thread(runnable);
    t1.start();
    t2.start();
  }
}

我确定这个序列可以被调用。
x.a() //in Thread-1
y.b() //in Thread-2

尽管我还有一个小的困惑,我们可以轻松地看到x.a()也调用了actBusy()方法,这是一个静态方法。方法b()是一个静态同步方法,调用一个非同步的静态方法。当线程2获得类级别锁时,为什么来自线程1的actBusy()调用没有被阻塞呢?
我只是在逻辑上感到困惑,如果一个线程获得了类级别锁,那么该类的其他非同步静态方法仍然可以从其他方法(实例方法)中调用。为什么?

小提示,但应该是:x.a()ThreadDemo.b() - Duncan Jones
4个回答

4
static synchronized 方法锁定的是类对象,而非静态 synchronized 方法锁定的是实例对象 (this) - 因此两种方法都可以并发调用,并且一个线程将运行第1个方法,而另一个线程将运行第2个方法。
但是,请注意,您的代码中不存在 竞态条件,因为竞态条件需要写入,而这些方法中不存在这样的情况。

4
那么为什么从Thread-1调用actBusy()没有被阻塞呢?
因为你的actBusy方法并没有被同步。即使你获得了类级别的锁,你仍然可以调用非同步的静态方法。
将方法标记为synchronized的目的是为了启用锁。只有声明为同步的方法才会受到这些锁的限制。因此,如果你获得了锁(假设是类级别的锁),那么任何一个非同步的方法都像以前一样,并不知道已经获得了锁。这使你能够决定哪些方法需要被阻塞,哪些不需要。

谢谢。这就是我在逻辑上感到困惑的地方,静态方法是类级别的方法,因此一旦类级别对象被锁定,为什么它允许调用其他静态方法?有什么逻辑背后吗? - benz
1
添加了一些解释 - Tala

2

actBusy()本身并不是同步的,但调用它的方法是同步的。

因此,线程1不会被阻塞,因为它获得了this对象的锁,并且没有其他线程持有this的锁,所以它可以无任何问题地调用它。

这是因为非静态同步方法在当前实例上锁定,而不是在class对象上锁定。

x.a()获取了当前实例即x的锁,除非当前线程释放了锁,否则没有其他线程能够进入xa()方法。

线程1 --> x.a() //获取锁并保持

线程2 ---> x.a() //在此处阻塞,直到线程1释放x的锁

编辑:

Class Object != Instance 

根据JMM的说法,它们是不同的对象,两个线程之间不会相互干扰。因此,您可以调用它。

编辑2:

为什么允许调用其他静态方法?有什么逻辑吗?

假设这样:

public static synchronized int statefulMethod(){
    //this should be protected
}

public static int nonStatefulMethod(){
    //Just returns a static value such as 5
    //so this is thread safe as it does not have any state
}

public static synchronized int otherStatefulMethod(){
    //this should also be thread safe
}

如果线程1正在执行具有共享状态需要保护的方法statefulMethod(),因此它使用类级别锁。现在,如果线程2调用了线程安全的nonStatefulMethod(),那么它逻辑上不应该被阻塞,因为没有必要在这里让该线程阻塞。
现在,如果线程3在线程1持有类锁的情况下调用otherStatefulMethod(),则线程3将不得不等待,因为该方法也使用了static-synchronized

我明白那个东西。我实际上正在寻找一个逻辑解释,即静态方法是类级别的方法,因此一旦类级别对象被锁定,为什么它允许调用其他静态方法?这背后有任何逻辑吗? - benz
我完全理解那部分。我的观点是,静态方法是类级别的方法而不是实例级别的方法。当b()被调用时,线程2获取了类级别锁。我唯一困惑的是,为什么Java允许在其他线程已经获取类锁的情况下调用其他静态方法。我知道它确实可以,但为什么呢?静态方法也是类级别的方法。 - benz
1
@benz 看看我在 EDIT 2 中的例子。你现在明白了吗? - Narendra Pathai
1
只有当静态方法带有synchronized时,它才会获取类级别对象。没有必要不必要地获取类级别对象。而synchronized关键字是告诉JVM获取锁的方式。 - Narendra Pathai
谢谢@Narendra,现在我明白了概念。非常感谢。 - benz
显示剩余3条评论

1

锁对象不是分层的。因此,在类本身上获取锁并不会取代对类实例的锁。它们是独立的锁对象,只会阻止试图锁定完全相同对象的代码。

因此,如果一个线程进入静态同步方法,那么唯一被阻塞的线程是那些也尝试在同一个类上进入静态同步方法的线程。仅尝试进入非静态同步方法的线程不受影响——它们只与尝试在同一对象实例上进入非静态同步方法的线程竞争。

关于您下面的评论——只有标记为synchronized的静态方法才受到类级别锁的限制。如果您想阻止其他静态方法,您还必须将它们标记为synchronized

为什么会这样呢?嗯,假设编译器认为您需要锁定所有静态方法只是因为其中一个标记为synchronized,那就太自作聪明了。假定程序员知道必须同步哪些方法以确保线程安全。


不,这不是我的困惑点。我的困惑点是,一旦线程获取了类级别锁,为什么仍然允许调用其他静态方法?我知道这是不允许的,但我在想,为什么会这样。 - benz
1
@benz 请参考上面的最后一段。 - Duncan Jones
只是一个愚蠢的问题,静态方法不是使用类级别对象调用的吗?因为在这个时候还没有“this”的存在。 - benz

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