如何跨类使用synchronized块?

时间:2014-07-16 02:02:22

标签: java multithreading synchronizing

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

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

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


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

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

7 个答案:

答案 0 :(得分:9)

拥有一个用作锁定的静态对象通常是不可取的,因为整个应用程序中一次只能有一个线程可以取得进展。如果你有多个类共享相同的锁,甚至更糟,你可以得到一个几乎没有实际并发性的程序。

Java在每个对象上具有内部锁定的原因是,对象可以使用同步来保护自己的数据。线程调用对象上的方法,如果需要保护对象不受并发更改的影响,那么可以将synchronized关键字添加到对象的方法中,这样每个调用线程必须先获取该对象的锁定才能执行方法在上面。这种方式调用不相关的对象不需要相同的锁,并且您有更好的机会实际同时运行代码。

锁定不一定是你的第一个并发技术。实际上,您可以使用许多技术。按降序排列:

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

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

3)使用并发库和框架优先于使用锁定滚动自己的对象。熟悉java.util.concurrent中的类。与应用程序开发人员可以管理的任何内容相比,它们编写得更好。

一旦你用上面的1,2和3做了尽可能多的事情,那么你可以考虑使用锁定(其中锁定包括ReentrantLock和内部锁定等选项)。将锁与受保护对象相关联可以最大限度地减小锁的范围,从而使线程不会长时间保持锁定。

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

完全避免内在锁定的建议可能是过早的优化。首先要确保你锁定正确的东西不超过必要的。

答案 1 :(得分:6)

选项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
      }
    }
  }

OPTION:2

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

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

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

答案 2 :(得分:3)

你的代码似乎对我有用,即使它看起来不那么好。但是请让你的对象在最后进行同步。 但是,根据您的实际情况,可能需要考虑一些因素。

以任何方式都应该在Javadocs中清楚地说明你要存档的内容。

另一种方法是在FirstClass上同步,例如

synchronized (FirstClass.class) {
// do what you have to do
} 

synchronized中的每个FirstClass方法都与上面的同步块相同。换句话说,它们也是synchronized在同一个对象上。 - 根据具体情况,可能会更好。

在其他情况下,如果您希望在数据库访问或类似操作上进行同步,那么您可能更喜欢某些BlockingQueue实现。

答案 3 :(得分:2)

我认为你的方法是错误的,完全使用synchronized块。从Java 1.5开始,有一个包java.util.concurrent可以让您对同步问题进行高级控制。

例如Semaphore类,它提供了一些只需要简单同步的基础工作:

Semaphore s = new Semaphore(1);
s.acquire();
try {
   // critical section
} finally {
   s.release();
}

即使是这个简单的类也比同步更多,例如tryAcquire()的可能性,它会立即返回是否获得锁定,并留给你选择做非关键工作,直到锁可用。

使用这些类也可以使您的对象更加清晰。虽然通用监视器对象可能会被误解,但默认情况下Semaphore与线程相关联。

如果你进一步查看concurrent-package,你会发现更多特定的同步类,如ReentrantReadWriteLock允许定义,可能有许多并发读操作,而只有write-ops实际上与其他读/写同步。您将找到一个Phaser,它允许您同步线程,以便同步执行特定任务(与synchornized相反)以及许多数据结构,这些数据结构可能根本不需要同步的情况。

总而言之:除非您确切知道为什么或者您坚持使用Java 1.4,否则不要使用普通synchronized。它很难阅读和理解,很可能你至少实现了SemaphoreLock的更高功能的部分内容。

答案 4 :(得分:2)

我想你想做的就是这个。您有两个工作类,它们对同一个上下文对象执行某些操作。然后,您希望锁定上下文对象上的两个worker类。然后,以下代码将适用于您。

public class Worker1 {

    private final Context context;

    public Worker1(Context context) {
        this.context = context;
    }

    public void someMethod(){
        synchronized (this.context){
            // do your work here
        }
    }
}

public class Worker2 {

    private final Context context;

    public Worker2(Context context) {
        this.context = context;
    }

    public void someMethod(){
        synchronized (this.context){
            // do your work here
        }
    }
}


public class Context {

    public static void main(String[] args) {
        Context context = new Context();
        Worker1 worker1 = new Worker1(context);
        Worker2 worker2 = new Worker2(context);

        worker1.someMethod();
        worker2.someMethod();
    }
}

答案 5 :(得分:2)

对于您的场景,我建议您编写一个Helper类,它通过特定方法返回监视器对象。方法名称本身定义了锁定对象的逻辑名称,这有助于您的代码可读性。

public class LockingSupport {
    private static final LockingSupport INSTANCE = new LockingSupport();

    private Object printLock = new Object();
    // you may have different lock
    private Object galaxyLock = new Object();

    public static LockingSupport get() {
        return INSTANCE;
    }

    public Object getPrintLock() {
        return printLock;
    }

    public Object getGalaxyLock() {
        return galaxyLock;
    }
}

在要强制执行同步的方法中,您可以要求支持人员返回相应的锁定对象,如下所示。

public static void unsafeOperation() {
    Object lock = LockingSupport.get().getPrintLock();
    synchronized (lock) {
        // perform your operation
    }
}

public void unsafeOperation2() { //notice static modifier does not matter
    Object lock = LockingSupport.get().getPrintLock();
    synchronized (lock) {
        // perform your operation
    }
}

以下几点优点:

  • 通过这种方法,您可以使用方法引用来查找正在使用共享锁的所有位置。
  • 您可以编写高级逻辑来返回不同的锁对象(例如,基于调用者的类包,为一个包的所有类返回相同的锁对象,但为其他包的类等返回不同的锁对象。)
  • 您可以逐步升级Lock实现以使用java.util.concurrent.locks.Lock API。如下图所示

e.g。 (更改锁定对象类型不会破坏现有代码,认为使用Lock对象作为同步(锁定)不是一个好主意)

public static void unsafeOperation2() {
    Lock lock = LockingSupport.get().getGalaxyLock();
    lock.lock();
    try {
        // perform your operation
    } finally {
        lock.unlock();
    }
}

希望有所帮助。

答案 6 :(得分:1)

首先,以下是您当前方法的问题:

  1. 锁定对象未被称为lock或类似。 (是的......挑剔)
  2. 变量不是final。如果意外(或故意)更改了obj,您的同步将会中断。
  3. 变量为public。这意味着其他代码可能会因获取锁定而导致问题。
  4. 我认为其中一些影响是你批评的根源:“这对我来说似乎很糟糕”。

    在我看来,这里有两个基本问题:

    1. 你有一个漏洞抽象。以任何方式(作为公共或包私有变量或通过getter)发布“class 1”之外的锁对象是暴露锁定机制。应该避免这种情况。

    2. 使用单个“全局”锁意味着您有并发瓶颈。

    3. 第一个问题可以通过抽象锁定来解决问题。例如:

      someMethod() {
           Class1.doWithLock(() -> { /* code */ });
      }
      

      其中doWithLock()是一个静态方法,它采用RunnableCallable或类似的方法,然后使用适当的锁运行它。 doWithLock()的实现可以根据其规范使用自己的private static final Object lock ...或其他一些锁定机制。

      第二个问题更难。摆脱“全局锁定”通常需要重新考虑应用程序体系结构,或者更改为不需要外部锁定的不同数据结构。