Chapter 02 - Thread Safety
What is threadsafe
A class is threadsafe when there is no state. For example
@ThreadSafe
public class Service {
public void printSomething() {
System.out.println("Hello world");
}
}
Given the following code
@NonThreadSafe
public class Service {
public int count = 0;
public void getUniqueCount() {
count += 1;
System.out.println("Count is: " + count);
}
}
Given this code, if the service class is executed in multiple thread, this would lead to a problem that each thread can have a different value of count.
Atomicity
The idea of atomicity is, action would be execute at once or not executing at all. This is good for the previous example since we need to make sure that it's executed correctly.
For the previous example, we can do something like
@ThreadSafe
public class Service {
public AtomicInteger count = new AtomicInteger();
public void getUniqueCount() {
int current = count.incrementAndGet();
System.out.println("Count is: " + current);
}
}
Given this code, the incrementAndGet
in background also do a compare and set (CAS) which compare if the value is the current value (not changed yet, and then set it to the next value). If the operation fail, it will re-attempt again
It do so by using VarHandle
which inturn execute low-level CPU instruction to do CAS.
Locking
However, atomicity will not work if we need to use more variables. For example:
@ThreadSafe
public class Service {
public AtomicInteger male = new AtomicInteger();
public AtomicInteger female = new AtomicInteger();
public void getUniqueCount(bool isMale) {
if (isMale) {
System.out.println("Total is": male.incrementAndGet() + female.get());
} else {
System.out.println("Total is": male.get() + female.incrementAndGet());
}
}
}
The reason is the incrementAndGet
is only threadsafe as a single atomic operation. When combining with other operation it's not threadsafe.
The solution is to use Locking
Intrinsic locks
Related Intrinsic lock. Only one item get locked at the same time.
In java we can use
synchronized (lock) {
}
We also have a shortcut to use with method
class Someclass {
private synchronized void method() {
// Will synchronized(this) on `this` object
}
}
[!danger]
So as a result, one class can only execute onesynchronized
method at the time if we use something like this Synchronised method only execute once a class
Example and optimise
Consider the following scenarios
@ThreadSafe
public class TicketService {
@GuardedBy("this") private int ticketCount = 100;
private final Logger logger = Logger.getLogger(TicketService.class.getName());
public synchronized boolean claimTicket(Request request) throws InterruptedException {
this.initialiseUserRequest(request);
if (ticketCount == 0) return false;
ticketCount -= 1;
return true;
}
private void initialiseUserRequest(Request request) throws InterruptedException {
// Simulate some initialisation logic for the user request
logger.info("Initialising request for " + request);
sleep(1000); // Simulate delay
}
}
This is works but bad performance. Since everyone needs to wait to claim the request.
Instead, we should only synchronise the needed part:
@ThreadSafe
public class TicketService {
@GuardedBy("this") private int ticketCount = 100;
private final Logger logger = Logger.getLogger(TicketService.class.getName());
public boolean claimTicket(Request request) throws InterruptedException {
this.initialiseUserRequest(request);
synchronized (this) {
if (ticketCount == 0) {
return false;
}
ticketCount -= 1;
return true;
}
}
private void initialiseUserRequest(Request request) throws InterruptedException {
// Simulate some initialisation logic for the user request
logger.info("Initialising request for " + request);
sleep(1000); // Simulate delay
}
}
As a result, we can increase the performance of initialiseUserRequest
. Note that since we're already using synchronized
here, there is no need to put ticketCount
as AtomicInteger
Decisions of how big or small synchronisation block might require some trade off.
[!danger]
Holding the lock for long time might cause performance issue and liveness problems. Avoid doing so