如何跨类使用同步块?

2022-09-03 12:44:29

我想知道如何在类中使用同步块。我的意思是,我想在多个类中同步块,但它们都在同一对象上同步。我想到如何做到这一点的唯一方法是这样的:

//class 1
public static Object obj = new Object();

someMethod(){
     synchronized(obj){
         //code
     }
}


//class 2
someMethod(){
     synchronized(firstClass.obj){
         //code
     }
}

在此示例中,我创建了一个任意 Object,以便在第一个类中进行同步,并在第二个类中通过静态引用来同步它。但是,这对我来说似乎是糟糕的编码。有没有更好的方法来实现这一目标?


答案 1

通常不需要将静态对象用作锁,因为整个应用程序中一次只有一个线程可以取得进展。当您有多个类都共享同一个锁时,情况会更糟,您最终可能会得到一个几乎没有实际并发性的程序。

Java 在每个对象上都有固有锁的原因是,对象可以使用同步来保护自己的数据。线程调用对象上的方法,如果需要保护对象免受并发更改的影响,则可以将 sync 关键字添加到对象的方法中,以便每个调用线程必须先获取该对象上的锁定,然后才能对其执行方法。这样,对不相关对象的调用就不需要相同的锁,并且您有更好的机会让代码实际并发运行。

锁定不一定是您实现并发的第一个首选技术。实际上,您可以使用许多技术。按降序优先顺序:

1)尽可能消除可变状态;不可变对象和无状态函数是理想的选择,因为没有要保护的状态,也不需要锁定。

2)尽可能使用螺纹限制;如果可以将状态限制为单个线程,则可以避免数据争用和内存可见性问题,并最大程度地减少锁定量。

3)使用并发库和框架,而不是使用锁定滚动自己的对象。熟悉 java.util.concurrent 中的类。这些比应用程序开发人员可以设法放在一起的任何东西都写得更好。

一旦你尽可能多地完成了上面的1,2和3,那么你可以考虑使用锁定(其中锁定包括诸如ReentrantLock以及固有锁定之类的选项)。将锁与受保护的对象相关联可最大程度地减小锁的作用域,以便线程持有锁的时间不会超过其需要的时间。

此外,如果锁定不在被锁定的数据上,那么如果在某些时候您决定使用不同的锁,而不是让所有内容都锁定在同一事物上,那么避免死锁可能具有挑战性。锁定需要保护的数据结构使锁定行为更容易推理。

完全避免固有锁的建议可能是过早的优化。首先,请确保您锁定了正确的东西,而不是不必要的事情。


答案 2

选项 1:

更简单的方法是使用枚举或静态内部类创建一个单独的对象(单例)。然后用它来锁定两个类,它看起来很优雅:

// use any singleton object, at it's simplest can use any unique string in double quotes
  public enum LockObj {
    INSTANCE;
  }

  public class Class1 {
    public void someMethod() {
      synchronized (LockObj.INSTANCE) {
        // some code
      }
    }
  }

  public class Class2 {
    public void someMethod() {
      synchronized (LockObj.INSTANCE) {
        // some code
      }
    }
  }

选项:2

您可以使用任何字符串,因为JVM确保它在每个JVM中只存在一次。唯一性是确保此字符串上不存在其他锁。不要使用此选项,这只是为了澄清概念。

     public class Class1 {
    public void someMethod() {
      synchronized ("MyUniqueString") {
        // some code
      }
    }
  }

   public class Class2 {
        public void someMethod() {
          synchronized ("MyUniqueString") {
            // some code
          }
        }
      }