user3843164
user3843164

Reputation: 379

How to use synchronized blocks across classes?

I want to know how to use synchronized blocks across classes. 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. However, this seems like bad coding to me. Is there a better way to achieve this?

Upvotes: 16

Views: 8017

Answers (7)

Gaurava Agarwal
Gaurava Agarwal

Reputation: 974

OPTION 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 a singleton object, at its simplest you 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:

you can use any string as JVM makes sure it's only present once per 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
        }
    }
}

Upvotes: 6

Stephen C
Stephen C

Reputation: 718826

First of all, here are the issues with your current approach:

  1. The lock object is not called lock or similar. (Yes ... a nitpick)
  2. The variable is not final. If something accidentally (or deliberately) changes obj, your synchronization will break.
  3. The variable is 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. 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. The implementation of doWithLock() can use its own private static final Object lock ... or some other locking mechanism according to its specification.

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.

Upvotes: 2

skadya
skadya

Reputation: 4390

For your scenario, I can suggest you to write a Helper class which returns the monitor object via specific method. 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(e.g. 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.LockAPIs. as shown below

e.g. (changing lock object type will not break existing code, thought it is not good idea to use Lock object as synchronized( lock) )

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

Hopes it helps.

Upvotes: 2

Dulaj Atapattu
Dulaj Atapattu

Reputation: 446

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.

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

Upvotes: 3

Nathan Hughes
Nathan Hughes

Reputation: 96395

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. 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. 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; 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; 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. Get acquainted with the classes in 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). 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.

Upvotes: 12

Jonathan
Jonathan

Reputation: 2758

I think you are going the wrong way, using synchronized blocks at all. Since Java 1.5 there is the package java.util.concurrent which gives you high level control over synchronization issues.

There is for example the Semaphore class, which provides does some base work where you need only simple synchronization:

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.

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.

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

All-in-all: Don't use plain synchronized at all unless you know exactly why or you are stuck with Java 1.4. It is hard to read and understand and most probably you are implementing at least parts of the higher functions of Semaphore or Lock.

Upvotes: 2

Zarathustra
Zarathustra

Reputation: 2943

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.

Another approach is to sync on FirstClass e.g.

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

However every synchronized method in FirstClass is identical to the synchronized block above. With other words, they are also synchronized on the same object. - 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.

Upvotes: 3

Related Questions