Over a million developers have joined DZone.
{{announcement.body}}
{{announcement.title}}

Java Concurrency – Part 3 : Synchronization With Intrinsic Locks

DZone's Guide to

Java Concurrency – Part 3 : Synchronization With Intrinsic Locks

· Java Zone
Free Resource

Just released, a free O’Reilly book on Reactive Microsystems: The Evolution of Microservices at Scale. Brought to you in partnership with Lightbend.

After learning how to create threads and manipulate them, it’s time to go to one of the most important things: synchronization. Synchronization is a way to make some code thread safe. Code that can be accessed by multiple threads must be made thread safe. Thread Safe describes some code that can be called from multiple threads without corrupting the state of the object or simply doing the thing that the code must do in right order.

For example, we can take this little class :

public class Example {
private int value = 0;

public int getNextValue(){
return value++;
}
}

It’s really simple and works well with one thread, but absolutely not with multiple threads. An incrementation like this is not a simple action, but three actions :

  • Read the current value of “value”
  • Add one to the current value
  • Write that new value to “value”

Normally, if you have two threads invoking the getNextValue(), you might  think that the first will get 1 and the next will get 2, but it is possible that the two threads get the value 1. Imagine this situation :

Thread 1 : read the value, get 0, add 1, so value = 1
Thread 2 : read the value, get 0, add 1, so value = 1
Thread 1 : write 1 to the field value and return 1
Thread 2 : write 1 to the field value and return 1

These situations come from what we call interleaving. Interleaving describes the possible situations of several threads executing some statements. Even when we only consider three operations and two threads, there are still a lot of possible interleavings.

So we must made the operations atomic to work with multiple threads. In Java, the first way to make that is to use a lock. All Java objects contain intrinsic locks, we’ll use that lock to make methods or statement atomic. When a thread has a lock, no other thread can acquire it and must wait for the first thread to release the lock. To acquire the lock, you have to use the synchronized keyword to automatically acquire and release a lock for a code. You can add the synchronized keyword to a method to acquire the lock before invoking the method and release it after the method execution. You can refactor the getNextValue() method using the synchronized keyword :

public class Example {
private int value = 0;

public synchronized int getNextValue(){
return value++;
}
}

With that, you have the guarantee that only thread can execute the method at the same time. The used lock is the intrinsic lock of the instance. If the method is static, the used lock is the Class object of Example. If you have two methods with the synchronized keyword, only one method of the two will be executed at the same time because the same lock is used for the two methods. You can also write it using a synchronized block :

public class Example {
private int value = 0;

public int getNextValue() {
synchronized (this) {
return value++;
}
}
}

This is exactly the same as using the synchronized keyword on the method signature. Using synchronized blocks, you can choose the lock to block on. For example, if you don’t want to use the intrinsic lock of the current object but an other object, you can use an other object just as a lock :

public class Example {
private int value = 0;

private final Object lock = new Object();

public int getNextValue() {
synchronized (lock) {
return value++;
}
}
}

The result is the same but has one difference, the lock is internal to the object so no other code can use the lock. With complex classes, it not rare to use several locks to provide thread safety on the class.

There is an other issue with multiple threads : the visibility of the variables. This happens when a change made by a thread is visible by an other thread. For performance improvements, the Java compiler and virtual machines can make some improvements using registers and a cache. By default, you have no guarantee that a change made by a thread is visible to another thread. To make a change visible to another thread, you must use synchronized blocks to ensure the visibility of the change. You must use synchronized blocks for the read and for the write of the shared values. You must do that for every read/write of a value shared between multiple threads.

You can also use the volatile keyword on the field to ensure the visibility of read/write between multiple threads. The volatile keyword ensure only visibility, not atomicity. The synchronized blocks ensure visibility and atomicity. So you can use the volatile keyword on fields that doesn’t need atomicity (if you make only read and write to the field without depending on the current value of the field by example).

Pay attention that trying to solve thread safety on a problem can add new issues of deadlock. For example, if thread A owns the lock 1 and are waiting for the lock 2 and if lock 2 is acquired by thread B who waits on lock 1, there is a deadlock. Your program is dead. So you have to pay great attention to the locks.

There is several rules that we must keep in mind when using locks :

  1. Every mutable field shared between multiple threads must be guarded with a lock or made volatile, if you only need visibility
  2. Synchronize only the operations that must synchronized, this improves performance. But don’t synchronize too few operations. Try to keep the lock only for short operations.
  3. Always know which locks are acquired and when there are acquired and by which thread
  4. An immutable object is always thread safe

Here we are, I hope that this post helps you to understand thread safety and how to achieve it using intrinsic locks. In the next posts, we’ll see another synchronization methods.

From http://www.baptiste-wicht.com/2010/08/java-concurrrency-synchronization-locks

Strategies and techniques for building scalable and resilient microservices to refactor a monolithic application step-by-step, a free O'Reilly book. Brought to you in partnership with Lightbend.

Topics:

Opinions expressed by DZone contributors are their own.

{{ parent.title || parent.header.title}}

{{ parent.tldr }}

{{ parent.urlSource.name }}