15

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

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

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


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

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

4

7 回答 7

10

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

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

锁定不一定是您首选的并发技术。实际上,您可以使用多种技术。按偏好降序排列:

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

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

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

一旦你尽可能多地完成了上面的 1、2 和 3,那么你可以考虑使用锁定(其中锁定包括 ReentrantLock 和内部锁定等选项)。将锁与被保护的对象相关联可以最小化锁的范围,这样线程就不会持有超过它需要的锁。

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

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

于 2017-09-26T02:24:37.850 回答
5

选项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
          }
        }
      }
于 2017-09-25T10:09:43.193 回答
3

您的代码对我来说似乎是有效的,即使它看起来不太好。但是请使您正在同步的对象最终同步。但是,根据您的实际情况,可能会有一些注意事项。

无论如何都应该在 Javadocs 中明确说明您想要归档的内容。

另一种方法是同步FirstClass例如

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

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

BlockingQueue在其他情况下,如果您想要在数据库访问或类似情况下同步,也许您更喜欢一些实现。

于 2014-07-16T02:25:27.487 回答
3

我想你想做的是这个。你有两个对同一个上下文对象执行一些操作的工作类。然后你想锁定上下文对象上的两个工作类。那么下面的代码将为你工作。

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();
    }
}
于 2017-10-02T13:32:31.623 回答
2

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

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

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

即使这个简单的类给你提供的不仅仅是同步的,例如,tryAcquire()无论是否获得锁,它都会立即返回,并留给你做非关键工作的选项,直到锁可用。

使用这些类还可以更清楚地了解您的对象的用途。虽然通用监视器对象可能会被误解,但Semaphore默认情况下 a 是与线程相关联的东西。

如果您进一步查看并发包,您会发现更具体的同步类,例如ReentrantReadWriteLock允许定义的可能有许多并发读操作,而实际上只有写操作与其他读/写同步。您会发现一个Phaser允许您同步线程以便同步执行特定任务(与synchornized.

总而言之:synchronized除非您确切知道原因或您被 Java 1.4 所困,否则根本不要使用 plain。它很难阅读和理解,而且很可能您正在实现Semaphoreor的至少部分高级功能Lock

于 2017-09-28T09:11:22.287 回答
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.LockAPI。如下所示

例如(更改锁定对象类型不会破坏现有代码,认为将锁定对象用作同步(锁定)不是一个好主意)

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

希望它有所帮助。

于 2017-10-02T21:37:45.533 回答
1

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

  1. 锁定对象未调用lock或类似。(是的......挑剔)
  2. 变量不是final。如果某些事情意外(或故意)更改obj,您的同步将中断。
  3. 变量是public。这意味着其他代码可能会通过获取锁而导致问题。

我想其中一些影响是您批评的根源:“这对我来说似乎是糟糕的编码”。

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

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

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

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

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

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

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

于 2017-10-01T02:32:54.233 回答