10

I have a requirement of locking several objects in one method in my java class. For an example look at the following class:

public class CounterMultiplexer {

    private int counter =0;
    private int multiPlexer =5;
    private Object mutex = new Object();

    public void calculate(){

        synchronized(mutex){
            counter ++;
            multiPlexer = multiPlexer*counter;
        }
     }

   public int getCounter(){
      return counter;
   }
   public int getMux(){
      return multiPlexer;
   }
}

In the above code, I have two resources that could access by a more than one thread. Those two resources are counter and the multiPlexer properties. As you can see in the above code I have locked both the resources using a mutex.

Is this way of locking is correct? Do I need to use nested Synchronized statements to lock both resources inside the calculate method?

JensG
  • 13,148
  • 4
  • 45
  • 55

5 Answers5

4

So you've got the idea of mutex (and atomicity) correct. However there's an additional wrinkle in the Java memory model which is visibility that you have to take into consideration.

Basically, both reads and writes must be synchronized, or the read is not guaranteed to see the write. For your getters, it would be very easy for the JIT to hoist those values into a register and never re-read them, meaning the value written would never be seen. This is called a data race because the order of the write and the read cannot be guaranteed.

To break the data race, you have to use memory ordering semantics. This boils down to synchronizing both the reads and the writes. And you have to do this every time you need to use synchronization anywhere, not just in the specific case you have above.

You could use almost any method (like AtomicInteger) but probably the easiest is either to re-use the mutex you already have, or to make the two primitive values volatile. Either works, but you must use at least one.

public class CounterMultiplexer {

    private int counter =0;
    private int multiPlexer =5;
    private Object mutex = new Object();

    public void claculate(){

        synchronized(mutex){
            counter ++;
            multiPlexer = multiPlexer*counter;
        }
     }

   public int getCounter(){
      synchronized(mutex){
        return counter;
     }
   }

   public int getMux(){
      synchronized(mutex){
        return multiPlexer;
      }
   }
}

So to get into this more, we have to read the spec. You can also get Brian Goetz's Java Concurrency in Practice which I highly recommend because he covers this sort of thing in detail and with simple examples that make it very clear that you must syncrhonize on both reads and writes, always.

The relevant section of the spec is Chapter 17, and in particular section 17.4 Memory Model.

Just to quote the relevant parts:

The Java programming language memory model works by examining each read in an execution trace and checking that the write observed by that read is valid according to certain rules.

That bit is important. Each read is checked. The model doesn't work by checking the writes alone and then assuming the reads can see the write.

Two actions can be ordered by a happens-before relationship. If one action happens-before another, then the first is visible to and ordered before the second.

The happens-before is what allows reads to see a write. Without it, the JVM is free to optimize your program in ways that might preclude seeing the write (like hoisting a value into a register).

The happens-before relation defines when data races take place.

A set of synchronization edges, S, is sufficient if it is the minimal set such that the transitive closure of S with the program order determines all of the happens-before edges in the execution. This set is unique.

It follows from the above definitions that:

An unlock on a monitor happens-before every subsequent lock on that monitor.

A write to a volatile field (§8.3.1.4) happens-before every subsequent read of that field.

So happens-before defines when a data race does (or does not) take place. How volatile works I think is obvious from the description above. For a monitor (your mutex), it's important to note that happens-before is established by a unlock followed by a later lock, so to establish happens-before for the read, you do need to lock the monitor again just before the read.

We say that a read r of a variable v is allowed to observe a write w to v if, in the happens-before partial order of the execution trace:

r is not ordered before w (i.e., it is not the case that hb(r, w)), and

there is no intervening write w' to v (i.e. no write w' to v such that hb(w, w') and hb(w', r)).

Informally, a read r is allowed to see the result of a write w if there is no happens-before ordering to prevent that read.

"Allowed to observe" means the read actually will see the write. So happens-before is what we need to see the write, and either the lock (mutex in your program) or volatile will work.

There's lots more (other things cause happens-before) and there's the API too with classes in java.utli.concurrent that will also cause memory ordering (and visibility) semantics. But there's the gory details on your program.

Community
  • 1
  • 1
markspace
  • 10,621
  • 3
  • 25
  • 39
3

No you don't need to use nested synchronized statements to lock both resource inside the calculate method. But you need to add synchronized clause in get methods also, synchronization is needed for both reading/writing into the resource.

 public int getCounter(){
 synchronized(mutex){
      return counter;
}

   }
   public int getMux(){
 synchronized(mutex){
      return multiPlexer;
}
   }
HPCS
  • 1,434
  • 13
  • 22
  • 1
    This additional synchronization is not necessary for primitive values. There is no need for the getters to wait for a change since the getter will either get the old or new value and the synchronization doesn't change that. – Ryan Cogswell Nov 02 '18 at 14:29
  • 3
    You are not right, this would be true if the primitive would be volatile. So please revoke the downvote. – HPCS Nov 02 '18 at 14:31
  • 2
    That makes no sense, Jose. The Java spec requires synchronization on both reads and writes, otherwise the synchronization is broken. Please read the spec. – markspace Nov 02 '18 at 14:36
  • 1
    @Jose, I dont understand what you mean, if you don't declare the primitive variable volatile or not reading under synchronized, than its possible that you never see the new value. – HPCS Nov 02 '18 at 14:37
  • Synchronization on a simple getter has no effect except to cause the caller to wait if another thread is in the middle of the calculate method. What are you trying to protect with the synchronization on the getter? – Ryan Cogswell Nov 02 '18 at 14:39
  • 1
    @RyanC You need synchronization or you need to set the variable as volatile. If the variable is not volatile and you read it without the synchronized, than is possible that you will never see the new value in another thread. – HPCS Nov 02 '18 at 14:41
  • @HPCS - yes, I already mentioned that it should be volatile. If it is not volatile, I believe other threads could see the old value even if you synchronize on the mutex. – Ryan Cogswell Nov 02 '18 at 14:44
  • 1
    @RyanC That is **NOT CORRECT**. The Java spec requires that both READS and WRITES be synchronized. The Java memory model is not based for example on just write memory barriers. A barrier is required on the read as well. Please get Brian Goetz's book *Java Concurrency in Practice* and read it, you're missing half the story. – markspace Nov 02 '18 at 14:44
  • If you have synchronize blocks on both reads and write, you do not need to make the field volatile. If you want to avoid these blocks, you need to make the field volatile. – Thilo Nov 02 '18 at 14:45
  • @markspace - Java is not like C where reading something in the middle of change can cause you to see a corrupted value. In Java you will either get the old value or the new value (when dealing with a primitive as opposed to a multi-step change to an object). Java doesn't require any synchronization. Synchronization is only necessary to protect against race conditions. What race condition does synchronization on the getter help with? – Ryan Cogswell Nov 02 '18 at 14:48
  • @JoseMartinez, actually, `volatile` works on all of memory. When thread A updates a `volatile` variable, that doesn't only guarantee that thread B can see the update, it also guarantees that _everything_ thread A did before it wrote the variable will become visible to thread B after thread B reads the variable. – Solomon Slow Nov 02 '18 at 14:50
  • 1
    @RyanC, I think that what markspace meant by "you're just plain wrong" is that the Java Language Spec does _not_ make that guarantee for `long` or `double` values. Relaxing the requirement for `long` and `double` makes it easier to implement an efficient JVM on a 32-bit platform. – Solomon Slow Nov 02 '18 at 14:53
  • 1
    @RyanC: you're describing "word tearing" effects which indeed will never happen for `int` in Java. But even without word tearing, an unsynchronized read is not guaranteed to read the correct value, so the read is a race at minimum. And a non-volatile read may read a cached value always, so if you're calling `getCounter()` in a tight loop you might never observe the value changing at all. – Daniel Pryden Nov 02 '18 at 18:21
1

It is fine (better even) to use just a single mutex to protect both fields. The monitor object has nothing to do really with the fields or the object that holds them. In fact, it is good practice to use dedicated lock objects (instead of say this). You just have to make sure that all access to these fields end up using the same monitor.

However, it is not enough to wrap the setter in a synchronized block, all access to the (non-volatile) variables (including the getters) must be behind the same monitor.

Thilo
  • 257,207
  • 101
  • 511
  • 656
  • Thilo, can you explain, or point to literature, that would suggest requiring synchronization on the getter to the primitives? – Jose Martinez Nov 02 '18 at 14:36
  • https://stackoverflow.com/questions/106591/do-you-ever-use-the-volatile-keyword-in-java?rq=1 – Thilo Nov 02 '18 at 14:39
  • 1
    This has nothing to do really with the field being primitive. Without any thread synchronization, there are no guarantees that other threads will see updates *ever*. This is a frequent problem with the popular `while(!stopped)` infinite loops in background threads. – Thilo Nov 02 '18 at 14:40
  • 2
    @JoseMartinez The literature is the Java specification: https://docs.oracle.com/javase/specs/jls/se11/html/jls-17.html#jls-17.4 Specifically that section 17.4 on the Java Memory Model and *happens-before* ordering. Also well known is Brian Goetz's book *Java Concurrency in Practice* where he goes into this in detail. – markspace Nov 02 '18 at 14:41
  • Thanks! Yeah I was reading the Java spec. Ultimately I agree with your assessment but for different reason. I agree in synchronizing the getters mainly to treat the two step process of updating the two primitives as an atomic step. But maybe thats the same as what you meant. – Jose Martinez Nov 02 '18 at 14:48
1

Since the counter and the multiPlexer are locked simultaneously, they can be considered as a single resource. Moreover, the whole instance of the class CounterMultiplexer can be thought of as a single resource. In Java, considering an instance as a single resource is a most widespread approach. For this case, special synchronozed methods were introduced:

public synchronized void claculate(){
        counter ++;
        multiPlexer = multiPlexer*counter;
 }

public synchronized int getCounter(){
    return counter;
}

public synchronized int getMux(){
    return multiPlexer;
}

The mutex variable is not needed anymore.

Alexei Kaigorodov
  • 13,189
  • 1
  • 21
  • 38
  • 1
    I assume the OP's example has been simplified. There are reasons to use an internal (private) lock, and the OP might have a reason. But for simplicity you're right, you can't beat just using the built-in monitor in every object and just using `synchronized` on the whole method. – markspace Nov 02 '18 at 18:39
0

An alternative way to approach this kind of problem is to have all your member variables be final and for the calculate method to return a new instance of CounterMultiplexer. This guarantees that any instance of CounterMultiplexer is always in a consistent state. Depending on how you use this class, this approach would likely require synchronization outside of this class.

Synchronizing within the getters still allows for another thread to read one of the two member variables from before the change and one from after.

Ryan Cogswell
  • 75,046
  • 9
  • 218
  • 198
  • 1
    Using `final` (which makes the object immutable) seems to violate the spirit of the question though. And since `final` itself has memory visibility semantics, it does not "likely require synchronization outside of this class" as you suggest. In fact the spec is explicit that immutable objects don't require further synchronization. – markspace Nov 02 '18 at 15:53
  • The synchronization that would be required would be regarding references to CounterMultiplexer if those references then get changed to point at the new instance. – Ryan Cogswell Nov 02 '18 at 15:55