Chapter 03 - Sharing Object
Always synchronisation when share variable cross threads
package org.example.chapter3;
public class NoVisibility {
private static boolean ready;
private static int value;
private static class Reader extends Thread {
@Override
public void run() {
while (true) {
if (ready) {
System.out.println(value);
return;
}
Thread.yield();
}
}
}
public static void main(String[] args) {
var r = new Reader();
r.start();
value = 42;
ready = true;
}
}
Given this example, in theory the system should return 42
. However, the following could happen
- The thread loop forever: JVM decides to not make the variable
ready
pass to the Reader class for optimsiation - Print out
0
. JVM changes the order of the code for optimisation, so it executes theready
first before the value.
This can be avoid by applying proper synchronisation to our variable ready
and value
even though this is read only in the thread.
[!danger]
Without synchronisation, the compiler, processor can change the order of the operations.
Stale data
Another reason why we need to use Synchronisation for read is reader might get stale data.
Consider the following example:
@NotThreadSafe
public class MutableInteger {
private int value;
public int get() { return value; }
public void set(int value) { this.value = value; }
}
If ont thread call set()
the same time as the other thread call get()
, the data will be stale. As a result, we need to synchronise it
@ThreadSafe
public class SynchronizedInteger {
@GuardedBy("this") private int value;
public synchronized int get() { return value; }
public synchronized void set(int value) { this.value = value; }
}
Non-atomic 64-bit operation
When synchronsiation
is not applied to a variable, a thread might see a stale value but at least it will be one of the previous values.
This is true for all the variables except 64 bit variables like long
, double
if these long
and double
is not declared as volatile
The reason is with 64-bit variable, JVM allowed to treat it as 2 32-bit operations.
So if the next thread read a long
or double
and we dont have synchronisation
or volatile
, it could fetch the 32bit of the last value, and then get updated, and then fetst the 32bit of the new value. Therefore led to unexpected result
Locking and visibility
The idea is ThreadB
and ThreadA
needs to lock on the same lock so that it's guarantee that all threads have the visibility of the variables.
Volatile
See also: Volatile
volatile
is a lighter form of synchronisation that allows visibility but not atomicity whereas synchronized
allow both
When volatile
is applied to a variable, JVM will
- Avoid storing that variable to cache or register
- Does not reorder this variable
So in the previous example, we can avoid synchronise get and use volatile instead to improve performance
@ThreadSafe
public class SynchronizedInteger {
@GuardedBy("this") private volatile int value;
public int get() { return value; }
public synchronized void set(int value) { this.value = value; }
}
When using volatile
variable, we need to keep in mind
- Make sure that write variable does not depends on current value (
value ++
) if this is the case, consider usingAtomic
class - Locking is not required for any other reasons while the variable being accessed.
Publishing and Escaping
Publishing is when we intentionally expose the information to other threads or classes
Escaping is when we don't want to expose the variable or information, but it get exposed. In this case we called it escaping
Publishing example
One way to publish is we can just set the variable to be public static
public static Set<Secret> knownSecrets;
public void initialize() {
knownSecrets = new HashSet<Secret>();
}
Escaping example
Sometimes we can unintentionally let the variable to escape, for example
class UnsafeStates {
private String[] states = new String[] {
"AK", "AL" ...
};
public String[] getStates() { return states; }
}
In here we publish the states, but we escape the mutability of the private
field. A better way is to publish the Unmodifiable
version or simply states.clone()
Escaping via inheritance
Escaping can also be applied when the infomation of the class get accessed before initialisation happen. The easiest way to reproduce this is via inheritance overriding
class Student {
private String name;
public Student() {
doSomeInitialising();
printInfo();
}
protected void doSomeInitialising() {
this.name = "student";
}
void printInfo() {
System.out.println("Student name is: " + this.name);
}
}
class OverrideStudent extends Student {
@Override
protected void doSomeInitialising() {
// Ignore
}
}
public static void main(String[] args) {
OverrideStudent overrideStudent = new OverrideStudent();
}
Will print Student name is: null
. This is because the Student
is not initialised properly since it's dependent on the doSomeInitialising
method.
Solutions:
- Make
doSomeIntialising()
private - or make
doSomeInitialising()
final
Both of these actions will not allow the parents to override the method that's doing the initialising.
Escaping via anonymous class
Escaping can also be found if we expose it to external classes during constructor, for example
public class ThisEscape {
private final int num; // this field is 'escaped' since we have not initialised it before registering the listener
public ThisEscape(EventSource source) {
source.registerListener(
new EventListener() {
public void onEvent(Event e) {
doSomething(e);
}
});
num = 42;
}
void doSomething(Event e) {
// Handle the event
if (num != 42) {
throw new IllegalStateException("num is not initialized properly");
}
}
}
public class EventSource {
void registerListener(EventListener listener) {
listener.onEvent(new Event());
}
}
public static void main(String[] args) {
var eventSource = new EventSource();
ThisEscape thisEscape = new ThisEscape(eventSource);
}
As the result in here, when source.registerListener
called, it will also call the doSomething()
in anonymous class.
This doSomething()
belong to the main class, however we unintentionally let it trigger before our constructor finished.
Safe constructor practice
The previous example can be modified as following:
public class SafeEscape {
final int num;
final EventListener listener;
private SafeEscape() {
num = 42;
listener = new EventListener() {
@Override
public void onEvent(Event e) {
doSomething();
}
};
}
public static SafeEscape initalise(EventSource eventSource) {
var safeEscape = new SafeEscape(); // Make sure initialise happen
eventSource.registerListener(safeEscape.listener);
return safeEscape;
}
void doSomething() {
if (num != 42) {
throw new IllegalStateException("num is not initialized properly");
}
System.out.println("Success, num is: " + num);
}
}
public static void main(String[] args) {
var eventSource = new EventSource();
// ThisEscape thisEscape = new ThisEscape(eventSource);
SafeEscape safeEscape = SafeEscape.initalise(eventSource);
}
Success, num is: 42
We use a private constructor to make sure that only we can access this constructor. And then in the initialise we make sure that it's final
so it cannot be override when there is a new parent class.
Thread confinement
Thread confinement means every variable within a thread will stay in that thread and not share to another thread. This is the easiest way to achieve threadsafe by design.
Some of the example like JDBC Connection
class. The connection pool only return the connection when the connection finished executing. So other threads can't reuse the same connection. That connection is threadsafe by design.
Ad-hoc thread confinement
Developer ensures that a particular object is accessed by only one thread at a time.
volatile
is a special case of ad-hoc thread confinement where the developer can confine only 1 modification but not confine the read.
Stack confinement
Make sure that we only use local variable, therefore the variable only stays in the thread itself and not shared
ThreadLocal
ThreadLocal is a way to confine a variable uniquely for a thread. Consider the following example
public class ThreadLocalDemo {
static class ParallelWork {
private static ThreadLocal<List<Integer>> localInteger = new ThreadLocal<>() {
public List<Integer> initialValue() {
return List.of(1);
}
};
public static void getAndSetInteger() {
int value = localInteger.get().getFirst();
System.out.printf("Current integer %s\n", value);
localInteger.set(List.of(value + 1));
}
}
public static void main(String[] args) {
var pool = Executors.newFixedThreadPool(3);
pool.submit(ParallelWork::getAndSetInteger);
pool.submit(ParallelWork::getAndSetInteger);
pool.submit(ParallelWork::getAndSetInteger);
pool.submit(ParallelWork::getAndSetInteger);
pool.shutdown();
pool.close();
}
}
Current integer 1
Current integer 1
Current integer 1
Current integer 2
The localInteger
is unique for each thread, therefore it's automatically threadsafe. Since we have 3 threads and 4 calls. One of them will have result of 2.
You can think of this as Map<Thread, T>
Immutability
Another way is to make the object immutable so that it's threadsafe
@Immutable
public final class ThreeStooges {
private final Set<String> stooges = new HashSet<String>();
public ThreeStooges() {
stooges.add("Moe");
stooges.add("Larry");
stooges.add("Curly");
}
public boolean isStooge(String name) {
return stooges.contains(name);
}
}
Note that an immutable object can be made of non-immutable object (stooges
) in this case.
However the object is still immutable since there is no way to modify this object.
Final field
Make the field final can also help with immutability. It reduce the possible actions to mutate the fields (however the actual object can still be mutable).
It's a good practice to make all field final
whenever possible
Example: using volatile to publish immutable objects
Suppose that we have a function that can cache the factor of the last number (only the last number, it doesn't cache the previous one).
We can use volatile
and final
to do this
@Immutable
class OneValueCache {
private final BigInteger lastNumber;
private final BigInteger[] lastFactors;
public OneValueCache(BigInteger i,
BigInteger[] factors) {
lastNumber = i;
lastFactors = Arrays.copyOf(factors, factors.length);
}
public BigInteger[] getFactors(BigInteger i) {
if (lastNumber == null || !lastNumber.equals(i))
// If i is not cached, force recalculate
return null;
else
return Arrays.copyOf(lastFactors, lastFactors.length);
}
}
@ThreadSafe
public class VolatileCachedFactorizer implements Servlet {
private volatile OneValueCache cache = new OneValueCache(null, null);
public void service(ServletRequest req, ServletResponse resp) {
BigInteger i = extractFromRequest(req);
BigInteger[] factors = cache.getFactors(i);
if (factors == null) {
factors = factor(i);
cache = new OneValueCache(i, factors);
}
encodeIntoResponse(resp, factors);
}
}
The only reason why we can do this is the lastNumber
and the lastFactors
goes as pairs. Therefore, we can store 1 pair of volatile
last result. We can cache it.
This works because this example is unique situation and simple.
Unsafe Publication
Suppose we have this class
// Unsafe publication
public Holder holder;
public void initialize() {
holder = new Holder(42);
}
public class Holder {
private int n;
public Holder(int n) { this.n = n; }
public void assertSanity() {
if (n != n)
throw new AssertionError("This statement is false.");
}
}
In older version of java, assertSanity()
check can be false. Why? Assume multiple thread can access to holder
object.
ThreadA
will initialise the object, ThreadB
will try calling assertSanity()
.
In older java, constructor()
is not necessary the first thing to call before an object exists. Therefore ThreadB
could see n = 0
or n = null
as default value.
However from Java5+, it's by specification that constructor()
need to finish before an object is initalised. So any access to the object before that, object will be considered as null
To avoid this, we make the field holder to be final
. Since java has special way to treat final object that guarantee the object to exist
Safe publication idioms
To publish an object safely, make sure the following met
- Initialise from static field
- Store reference to
volatile
orAtomicReference
- Store refernce to a
final
field - Make sure that the object is properly constructed
- Guard the object in lock