One of the most important advice about multi threading is: don't try to improve performance on a particular area of code unless your performance measurements tell you to do so. In some cases, we may need to compromise good object oriented design to gain performance. But most of the time, good object oriented practices like encapsulation and immutability helps us to create thread-safe classes.
A class is thread-safe if it behaves correctly in the case of being accessed by multiple threads and it does not need any synchronization by the caller.
A class with no state (no field, no reference to another class) is thread safe, because it's methods use only local variables which live in the stack. And since each thread have their own stack, a thread cannot mess with a local variable of another.
++ count;
This is not an atomic operation. It does three things and it is an example of read-modify-write operation. The result is dependent on the initial value. But what if the inital value was not up to date? The result will be wrong.
Below is an example of a race condition for a Singleton implementation:
public class NotThreadSafe {
private ExpensiveObject instance = null;
public ExpensiveObject getInstance() {
if (instance == null)
instance = new ExpensiveObject();
return instance;
}
}
Here a thread may create the ExpensiveObject but is it visible to other threads? There is no guarantee. So 2 clients may get 2 different ExpensiveObject instances which is not very Singletonish.
One way of ensuring thread safety is by using a synchronized method:
public class SynchronizedFactorizer implements Servlet {
private BigInteger lastNumber;
private BigInteger[] lastFactors;
public synchronized void service(ServletRequest req,
ServletResponse resp) {
BigInteger i = extractFromRequest(req);
if (i.equals(lastNumber))
encodeIntoResponse(resp, lastFactors);
else {
BigInteger[] factors = factor(i);
lastNumber = i;
lastFactors = factors;
encodeIntoResponse(resp, factors);
}
}
}
But here, multiple threads will not be able to work at the same time and this may be a performance problem.
Reentrancy: Locks are acquired on a per-thread basis rather than per-invocation basis. So, when the same thread tries to gather the lock, this is okay. So the following code works just fine:
public class MovieLister {
public synchronized void doSomething() {
}
}
public class AdvancedMovieLister extends MovieLister {
public synchronized void doSomething() {
System.out.println("advanced stuff..");
super.doSomething();
}
}
Note: Using a synchronized method can be an overkill if the method contains lines that are not a problem for thread safety. So we should try to minimize synchronized parts by using synchronized blocks inside methods. We must also avoid holding locks during lenghty computations.
Memory Visibility Problems:
Lets have a look at the following code:
public class NoVisibility {
private static boolean ready;
private static int number;
private static class ReaderThread extends Thread {
public void run() {
while (!ready)
Thread.yield();
System.out.println(number);
}
}
public static void main(String[] args) {
new ReaderThread().start();
number = 42;
ready = true;
}
}
Here the ReaderThread is waiting for the ready flag, and at some point the main method sets it to true. BUT IS IT VISIBLE TO THE OTHER THREAD? There is no guarantee. So in some cases the output of println will be zero, in some cases the thread will not even terminate.
For a single thread, there is "as-if-serial" guarantee. But in reality, the order of the instuctions may change and for other threads there is no guarantee regarding the ordering.
What is the difference between synchronized collections and concurrent collections?
First of all, both are thread safe. Concurrent collections were introduced with Java 5. More than one threads can read and write concurrently in ConcurrentHashMap and still it provides thread-safety. ConcurrentHashMap divides the Map instance into different segments. And each thread acquires lock on each segment. On the contrary, SynchronizedMap internally wraps all the methods of Map interface with synchronized keyword. So everything is locked and this means a decrease in performance.
I strongly reccomend this book and some of the examples were taken from that book.
Comments