Synchronisation
We use synchronized
if we only want 1 Thread to perform the code while the others wait.
Method
We can put the keyword in a method, for example. Then all the thread, whenever this method is executed, it happens 1 thread at a time
public synchronized void calculate(String author) {
while (currentNumber < 1000) {
currentNumber *= 2;
System.out.printf("Current number %s from %s%n", currentNumber, author);
}
}
Running: test1
Running: test2
Running: test3
Current number 2 from test1
Current number 4 from test1
Current number 8 from test1
Current number 16 from test1
Current number 32 from test1
Current number 64 from test1
Current number 128 from test1
Current number 256 from test1
Current number 512 from test1
Current number 1024 from test1
Process finished with exit code 0
This is the same thing as
public void calculate(String author) {
synchronized (this) {
}
}
Block
We can run using synchronized
block to block out a code to run in single thread.
public void calculate(String author) {
System.out.printf("Running calculation for %s%n", author);
synchronized (this) {
while (currentNumber < 1000) {
currentNumber *= 2;
System.out.printf("Current number %s from %s%n", currentNumber, author);
}
}
}
Running: test1
Running: test2
Running calculation for test2
Running: test3
Running calculation for test3
Current number 2 from test2
Current number 4 from test2
Current number 8 from test2
Current number 16 from test2
Current number 32 from test2
Current number 64 from test2
Current number 128 from test2
Current number 256 from test2
Current number 512 from test2
Current number 1024 from test2
Running calculation for test1
So only the calculation block is run in single threaded.
Explaination
In here, we pass in this
object in synchronised
. This is monitor object. That means only 1 thread at the time can access to this object.
If for example, the method is static, then we are passing the class object instead:
class Calculator {
public static void calculate(String author) {
System.out.printf("Running calculation for %s%n", author);
synchronized (Calculator.class) {
while (currentNumber < 1000) {
currentNumber *= 2;
System.out.printf("Current number %s from %s%n", currentNumber, author);
}
}
}
}
[!note]
Forblock
, we can also choose to synchronise anyobject
or variables. This is because by default, java has an Instinct lock on each of the object.
Synchronisation on value class
For example if we have something like this:
@ThreadSafe
public class Synchronisation {
private static Integer mutualInteger = 0;
public static void main(String[] args) throws InterruptedException {
ExecutorService executorService = Executors.newFixedThreadPool(5);
IntStream.range(0,5).forEach((i) -> executorService.submit(Synchronisation::process));
executorService.shutdown();
}
public static void process() {
synchronized (mutualInteger) { // NOTE HERE
mutualInteger += 1;
System.out.println("Accessed " + mutualInteger + " from " + Thread.currentThread().getName());
}
}
}
Note that in this case we cannot synchronized
on value classes (Integer
).
[!important]
We cannot synchronize value classes such asInteger
,String
, …. It's because these values are immutable. And if it's immutable, the state cannot be changed, so thesynchronized
block cannot acquire instinct lock for this value.As a result, value classes like
Integer
,String
does not have instinct lock
A way to solve this is to add another Object
and Synchronize
that object instead:
@ThreadSafe
public class Synchronisation {
@GuardedBy("lock")
private static Integer mutualInteger = 0;
@GuardedBy("lock")
private static final Object lock = new Object();
public static void main(String[] args) throws InterruptedException {
ExecutorService executorService = Executors.newFixedThreadPool(5);
IntStream.range(0,5).forEach((i) -> executorService.submit(Synchronisation::process));
executorService.shutdown();
}
public static void process() {
System.out.println("In thread: " + Thread.currentThread().getName());
synchronized (lock) {
mutualInteger += 1;
System.out.println("Accessed " + mutualInteger + " from " + Thread.currentThread().getName());
}
// wait for lock to be acquired before doing this
System.out.println("Out thread: " + Thread.currentThread().getName());
}
}
In thread: pool-1-thread-1
In thread: pool-1-thread-4
In thread: pool-1-thread-2
In thread: pool-1-thread-5
In thread: pool-1-thread-3
Accessed 1 from pool-1-thread-1
Accessed 2 from pool-1-thread-3
Out thread: pool-1-thread-1
Out thread: pool-1-thread-3
Accessed 3 from pool-1-thread-5
Out thread: pool-1-thread-5
Accessed 4 from pool-1-thread-2
Out thread: pool-1-thread-2
Accessed 5 from pool-1-thread-4
Out thread: pool-1-thread-4