简体   繁体   English

如何跨类使用synchronized块?

[英]How to use synchronized blocks across classes?

I want to know how to use synchronized blocks across classes. 我想知道如何跨类使用synchronized块。 What I mean is, I want to have synchronized blocks in more than one class but they're all synchronizing on the same object. 我的意思是,我想在多个类中同步块,但它们都在同一个对象上进行同步。 The only way that I've thought of how to do this is like this: 我想到如何做到这一点的唯一方法是这样的:

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

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


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

In this example I created an arbitrary Object to synchronize on in the first class, and in the second class also synchronized on it by statically referring to it. 在这个例子中,我创建了一个在第一个类中同步的任意Object,在第二个类中也通过静态引用它来同步它。 However, this seems like bad coding to me. 但是,这对我来说似乎很糟糕。 Is there a better way to achieve this? 有没有更好的方法来实现这一目标?

Having a static object that is used as a lock typically is not desirable because only one thread at a time in the whole application can make progress. 具有用作锁的静态对象通常是不可取的,因为整个应用程序中一次只有一个线程可以取得进展。 When you have multiple classes all sharing the same lock that's even worse, you can end up with a program that has little to no actual concurrency. 如果你有多个类共享相同的锁,甚至更糟,你可以得到一个几乎没有实际并发性的程序。

The reason Java has intrinsic locks on every object is so that objects can use synchronization to protect their own data. Java在每个对象上具有内部锁定的原因是对象可以使用同步来保护自己的数据。 Threads call methods on the object, if the object needs to be protected from concurrent changes then you can add the synchronized keyword to the object's methods so that each calling thread must acquire the lock on that object before it can execute a method on it. 线程调用对象上的方法,如果需要保护对象不受并发更改的影响,则可以将synchronized关键字添加到对象的方法中,以便每个调用线程必须先获取该对象的锁定,然后才能对其执行方法。 That way calls to unrelated objects don't require the same lock and you have a better chance of having code actually run concurrently. 这种方式调用不相关的对象不需要相同的锁,你有更好的机会让代码实际并发运行。

Locking shouldn't necessarily be your first go-to technique for concurrency. 锁定不一定是您的第一个并发技术。 Actually there are a number of techniques you can use. 实际上,您可以使用许多技术。 In order of descending preference: 按降序排列顺序:

1) eliminate mutable state wherever possible; 1)尽可能消除可变状态; immutable objects and stateless functions are ideal because there's no state to protect and no locking required. 不可变对象和无状态函数是理想的,因为没有要保护的状态,也不需要锁定。

2) use thread-confinement where you can; 2)尽可能使用线程限制; if you can limit state to a single thread then you can avoid data races and memory visibility issues, and minimize the amount of locking. 如果您可以将状态限制为单个线程,则可以避免数据争用和内存可见性问题,并最大限度地减少锁定量。

3) use concurrency libraries and frameworks in preference to rolling your own objects with locking. 3)使用并发库和框架优先使用锁定来滚动自己的对象。 Get acquainted with the classes in java.util.concurrent. 熟悉java.util.concurrent中的类。 These are a lot better written than anything an application developer can manage to throw together. 与应用程序开发人员可以管理的任何内容相比,它们编写得更好。

Once you've done as much as you can with 1, 2, and 3 above, then you can think about using locking (where locking includes options like ReentrantLock as well as intrinsic locking). 一旦你完成了上面的1,2和3的尽可能多的工作,那么你可以考虑使用锁定(其中锁定包括ReentrantLock和内部锁定等选项)。 Associating the lock with the object being protected minimizes the scope of the lock so that a thread doesn't hold the lock longer than it needs to. 将锁与受保护对象相关联可以最大限度地减小锁的范围,从而使线程不会长时间保持锁定。

Also if the locks aren't on the data being locked then if at some point you decide to use different locks rather than having everything lock on the same thing, then avoiding deadlocks may be challenging. 此外,如果锁不在数据被锁定的情况下,那么如果在某些时候你决定使用不同的锁而不是让所有东西锁定在同一个东西上,那么避免死锁可能具有挑战性。 Locking on the data structures that need protecting makes the locking behavior easier to reason about. 锁定需要保护的数据结构使锁定行为更容易推理。

Advice to avoid intrinsic locks altogether may be premature optimization. 完全避免内在锁定的建议可能是过早优化。 First make sure you're locking on the right things no more than necessary. 首先要确保你锁定正确的东西不超过必要的。

OPTION 1: 选项1:

More simple way would be to create a separate object (singleton) using enum or static inner class. 更简单的方法是使用枚举或静态内部类创建单独的对象(单例)。 Then use it to lock in both the classes, it looks elegant: 然后使用它锁定两个类,它看起来很优雅:

// 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 OPTION:2

you can use any string as JVM makes sure it's only present once per JVM. 您可以使用任何字符串,因为JVM确保每个JVM只出现一次。 Uniqueness is to make sure no-other lock is present on this string. 唯一性是确保此字符串上没有其他锁定。 Don't use this option at all, this is just to clarify the concept. 根本不要使用此选项,这只是为了澄清这个概念。

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

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

Your code seems valid to me, even if it does not look that nice. 你的代码似乎对我有用,即使它看起来不那么好。 But please make your Object you are synchronizing on final. 但是请让你的对象在最后进行同步。 However there could be some considerations depending on your actual context. 但是,根据您的实际情况,可能会有一些考虑因素。

In any way should clearly state out in the Javadocs what you want to archive. 无论如何应该在Javadocs中清楚地说明你想要存档的内容。

Another approach is to sync on FirstClass eg 另一种方法是在FirstClass上同步,例如

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

However every synchronized method in FirstClass is identical to the synchronized block above. 但是, FirstClass每个synchronized方法都与上面的synchronized块相同。 With other words, they are also synchronized on the same object. 换句话说,它们也在synchronized一个对象上synchronized - Depending on the context it may be better. - 根据具体情况,可能会更好。

Under other circumstances, maybe you'd like to prefer some BlockingQueue implementation if it comes down that you want to synchronize on db access or similar. 在其他情况下,如果您希望在db访问或类似操作上进行同步,那么您可能更喜欢某些BlockingQueue实现。

I think what you want to do is this. 我想你想做的就是这个。 You have two worker classes that perform some operations on the same context object. 您有两个工作类,它们对同一个上下文对象执行某些操作。 Then you want to lock both of the worker classes on the context object.Then the following code will work for you. 然后,您希望锁定上下文对象上的两个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();
    }
}

I think you are going the wrong way, using synchronized blocks at all. 我认为你的方法是错误的,完全使用synchronized块。 Since Java 1.5 there is the package java.util.concurrent which gives you high level control over synchronization issues. 从Java 1.5开始, java.util.concurrent包就可以对同步问题进行高级控制。

There is for example the Semaphore class, which provides does some base work where you need only simple synchronization: 例如, Semaphore类提供了一些基本工作,只需要简单的同步:

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

even this simple class gives you a lot more than synchronized, for example the possibility of a tryAcquire() which will immediately return whether or not a lock was obtained and leaves to you the option to do non-critical work until the lock becomes available. 即使是这个简单的类也比同步更多,例如tryAcquire()的可能性,它会立即返回是否获得了一个锁,并且在锁变为可用之前向你提供做非关键工作的选项。

Using these classes also makes it clearer, what prupose your objects have. 使用这些类也可以使您的对象具有更清晰的特性。 While a generic monitor object might be misunderstood, a Semaphore is by default something associated with threading. 虽然通用监视器对象可能会被误解,但默认情况下, Semaphore与线程相关联。

If you peek further into the concurrent-package, you will find more specific synchronisation-classes like the ReentrantReadWriteLock which allows to define, that there might be many concurrent read-operations, while only write-ops are actually synchronized against other read/writes. 如果你进一步查看concurrent-package,你会发现更多特定的同步类,比如ReentrantReadWriteLock ,它允许定义可能有许多并发读操作,而只有write-ops实际上与其他读/写同步。 You will find a Phaser which allows you to synchronize threads such that specific tasks will be performed synchronously (sort of the opposite of synchornized ) and also lots of data structures which might make synchronization unnecessary at all in certain situations. 您将找到一个Phaser ,它允许您同步线程,以便同步执行特定任务(与synchornized的相反方式)以及在某些情况下可能根本不需要同步的许多数据结构。

All-in-all: Don't use plain synchronized at all unless you know exactly why or you are stuck with Java 1.4. 总而言之:除非您确切知道为什么或者您遇到Java 1.4,否则不要使用plain synchronized It is hard to read and understand and most probably you are implementing at least parts of the higher functions of Semaphore or Lock . 它很难阅读和理解,很可能你至少实现了SemaphoreLock的更高功能的一部分。

For your scenario, I can suggest you to write a Helper class which returns the monitor object via specific method. 对于您的场景,我建议您编写一个Helper类,它通过特定方法返回监视器对象。 Method name itself define the logical name of the lock object which helps your code readability. 方法名称本身定义了锁定对象的逻辑名称,这有助于您的代码可读性。

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;
    }
}

In your methods where you want to enforce the synchronization, you may ask the support to return the appropriate lock object as shown below. 在要强制执行同步的方法中,您可以要求支持人员返回相应的锁定对象,如下所示。

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

Below are few advantages: 以下是几个优点:

  • By having this approach, you may use the method references to find all places where the shared lock is being used. 通过这种方法,您可以使用方法引用来查找正在使用共享锁的所有位置。
  • You may write the advanced logic to return the different lock object(eg based on caller's class package to return same lock object for all classes of one package but different lock object for classes of other package etc.) 您可以编写高级逻辑来返回不同的锁对象(例如,基于调用者的类包,为一个包的所有类返回相同的锁对象,但为其他包的类等返回不同的锁对象)
  • You can gradually upgrade the Lock implementation to use java.util.concurrent.locks.Lock APIs. 您可以逐步升级Lock实现以使用java.util.concurrent.locks.Lock API。 as shown below 如下所示

eg (changing lock object type will not break existing code, thought it is not good idea to use Lock object as synchronized( lock) ) 例如(更改锁定对象类型不会破坏现有代码,认为使用Lock对象作为同步(锁定)不是一个好主意)

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

Hopes it helps. 希望它有所帮助。

First of all, here are the issues with your current approach: 首先,以下是您当前方法的问题:

  1. The lock object is not called lock or similar. 锁定对象不称为lock或类似。 (Yes ... a nitpick) (是的......挑剔)
  2. The variable is not final . 变量不是final If something accidentally (or deliberately) changes obj , your synchronization will break. 如果意外(或故意)改变了obj ,你的同步就会中断。
  3. The variable is public . 变量是public That means other code could cause problems by acquiring the lock. 这意味着其他代码可能会通过获取锁定导致问题。

I imagine that some of these effects are at the root of your critique: "this seems like bad coding to me". 我想这些影响中的一些是你批评的根源:“这对我来说似乎是不好的编码”。

To my mind, there are two fundamental problems here: 在我看来,这里有两个基本问题:

  1. You have a leaky abstraction. 你有一个漏洞的抽象。 Publishing the lock object outside of "class 1" in any way (as a public or package private variable OR via a getter) is exposing the locking mechanism. 以任何方式(作为公共或包私有变量或通过getter)发布“class 1”之外的锁对象是暴露锁定机制。 That should be avoided. 应该避免这种情况。

  2. Using a single "global" lock means that you have a concurrency bottleneck. 使用单个“全局”锁意味着您有一个并发瓶颈。

The first problem can be addressed by abstracting out the locking. 第一个问题可以通过抽象锁定解决。 For example: 例如:

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

where doWithLock() is a static method that takes a Runnable or Callable or similar, and then runs it with an appropriate lock. 其中doWithLock()是一个静态方法,它采用RunnableCallable或类似方法,然后使用适当的锁运行它。 The implementation of doWithLock() can use its own private static final Object lock ... or some other locking mechanism according to its specification. doWithLock()的实现可以根据其规范使用自己的private static final Object lock ...或其他一些锁定机制。

The second problem is harder. 第二个问题更难。 Getting rid of a "global lock" typically requires either a re-think of the application architecture, or changing to a different data structures that don't require an external lock. 摆脱“全局锁定”通常需要重新考虑应用程序体系结构,或者更改为不需要外部锁定的不同数据结构。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM