Safe Object Sharing
There are 2 alternatives to synchronizing objects to make sure that nothing breaks when sharing objects. Either the shared object is immutable which would lead to there never being any inconsistent states between the threads. The other alternative is you just don't have a shared state variable between threads.
Immutable Objects
In principle, immutable objects aren't very complicated. You do however have to be aware of how the object is initialized when working concurrently as we don't want half- or even non-initialized objects. For example, in the example below the account
object could be un- or partial-initialized.
// immutable
final class Account {
private int balance;
public Account(int balance) {
this.balance = balance;
}
public String toString() { return "" + balance; }
}
class Company {
private Account account = null;
public Account getAccount() { // lazy initialization
if(account == null) account = new Account(10000);
return account;
}
}
To illustrate how account
could break we can imagine that we have two threads, T0
and T1
. If we then call T0: company.getAccount().toString();
and T1: company.getAccount().toString();
we don't have a guaranty that we get 10000, we could also get 0. The reason for this is that there could be an interleaving between the object creation and the assignment of the balance
field, resulting in a partial-initialized object. To fix this we could make the account
field volatile. The happens-before relation then guarantees that fields set in the constructor are visible as the invocation of the constructor happens-before the assignment to the volatile field account
.
class Company {
private volatile Account account = null; // safe publication
public Account getAccount() {
if(account == null) account = new Account(10000);
return account;
}
}
Using volatile for this is very expensive as we have previously seen and means that the CPU can't make performance optimizations by caching values and we only really want the functionality of volatile for the first initialization and not for any further calls of getAccount()
. For this reason, the JMM guarantees that final fields are only visible after they have been initialized! This means that if a thread sees a reference to an Account instance, it has the
guarantee to see all the final fields fully initialized. The JMM also guarantees that if a reference of an object is final, all referenced objects are visible after initialization if accessed over the final reference.
class Account {
private final int balance;
public Account(int balance) { this.balance = balance; }
public String toString() { return "" + balance; }
}
Initialization-Safety is however only guaranteed if an object is accessed after it is fully constructed. For this to be the case you can not allow the this
reference to escape during construction. Some possible ways the this
reference could escape:
-
Publishing an instance of an inner class. This implicitly publishes the enclosing instance as well because the inner class instance contains a hidden reference to the enclosing instance. For example when registering an event listener from the constructor.
-
Starting a thread within a constructor. When an object creates a thread from its constructor, it almost always shares its
this
reference with the new thread. Either explicitly, by passing it to the constructor or implicitly, because the Thread or Runnable is an inner class of the owning object. The new thread might then be able to see the owning object before it is fully constructed. -
Calling an alien method in the constructor. An alien methods behavior is not fully specified by the invoking class because it is either in another class or an overridable method.
Thread Locals
The DateFormat Class (opens in a new tab) in Java is documented not to be thread-safe. Instead, it is recommended we use a fresh instance on every invocation or a separate instance for each thread.
public class BadFormatter {
private static final SimpleDateFormat sdf = new SimpleDateFormat();
public static String format(Date d) {
return sdf.format(d);
}
}
public class GoodFormatter {
public static String format(Date d) {
SimpleDateFormat sdf = new SimpleDateFormat();
return sdf.format(d);
}
}
In the solution above we are creating a fresh instance for each call which can be quite expensive. Instead, we can use the ThreadLocal class (opens in a new tab). A thread-local variable provides a separate copy of its value for each thread that uses it. It, therefore, provides a mechanism to pass state down the call stack without having to explicitly define an additional method parameter.
class ThreadLocal<T> {
public T get();
public void set(T value);
protected T initialValue();
public void remove();
}
We could then solve the above problem like the following
class ThreadLocalFormatter {
private static ThreadLocal<SimpleDateFormat> local = ThreadLocal.withInitial(() -> new SimpleDateFormat());
public static String format(Date d) {
return local.get().format(d);
}
}
ThreadLocalRandom
Although the Random class (opens in a new tab) is thread-safe the concurrent use of the same Random instance across threads may encounter thread contention (opens in a new tab) and consequently have poor performance. For this reason the ThreadLocalRandom class (opens in a new tab) was added.
// Usage
ThreadLocalRandom.current().nextInt() // current returns the current thread's ThreadLocalRandom instance