Rule 1:Synchronize access to shared mutable data
Defination:
There 2 Aspects of synchrnination – one commonly known another is caveat
Synchrnination is a way to achieve CONSISTENT STATE of OBJECT, by LOCKING OF OBJECT by method that accesses it.
AND the Second Point is:
it ensures that each thread entering a synchronized method or block sees the effects
of all previous modifications that were guarded by the same lock.(Java Memory Model)
Is it Required to encapsulate synchrnination on atomic field also?
ATOMIC data members of Class – i.e. Have No affect(integrity) of Concurrent access(atleast it is what the JLS says)
The IMPORTANT POINT HERE about atomic field IS:
While the language specification GUARENTEES that A THREAD will NOT SEE an
ARBITRARY VALUE when READING a field, it DOES NOT GUARENTEE that a value WRITTEN by ONE THREAD will be VISIBLE to another.
So,sync is required in atomic cases too.
EXAMPLE: Atomic data-members –
All VARIABLES are ATOMIC Except long and double.
CONSEQUENCE of NOT SYNCHRONIZING the SHARED MUTABLE data can be dire even if the data is ATOMIC:
Eaxmple: Stopping one thread from another-
One way is using the Library that provides Thread.stop() but it was deprecated long ago because it is inherently unsafe. – It’s use can result in data corruption so,
DO NOT USE Thread.stop() .
Recommended Way: to stop a thread from another
Have the first thread pool a boolean FIELD with which is initially false but can be set “true” by second thread to indicated the First thread to stop itself.
Wrong Assumption:
Because reading and writing a boolean field is atomic, some programmers dispense with synchronization when accessing the field.
Example : Broken Code (sync dispensed)
// Broken! – How long would you expect this program to run?
public class StopThread {
private static boolean stopRequested;
public static void main(String[] args)
throws InterruptedException {
Thread backgroundThread = new Thread(() -> {
int i = 0;
while (!stopRequested)
i++;
});
backgroundThread.start();
TimeUnit.SECONDS.sleep(1);
stopRequested = true;
}
}
Expectation : by a user(not system) as per the most user understanding –
the program to run for a second after which the main thread sets “stopRequested” to “true” causing the backgroung thread to terminate
Acutal:
however, on some machine the program never terminates:the background thread loops forever!
Reason:
In the absence of synchronization, there is NO GUARANTEE as
to WHEN, IF EVER, the BACKGROUND thread will SEE the CHANGE in the VALUE of
stopRequested made BY the MAIN THREAD.
Reason for NO GUARANTEE of changes reflecting to background thread:
In the absence of synchronization, it’s QUITE ACCEPTABLE for the VIRTUAL MACHINE to transform this code:
while (!stopRequested)
i++;
into this code:
if (!stopRequested)
while (true)
i++;
This optimization is known as HOISTING, and it is precisely what the OpenJDK
Server VM does. The result is a LIVENGNESS FAILURE: the program fails to make progress.
Solution 1: for HOISTING AND LIVENESS FAILURE –
SYNCHRONIZE access to the stopRequested field.
This program terminates in about one second, as expected:
// Properly synchronized cooperative thread termination
public class StopThread {
private static boolean stopRequested;
private static synchronized void requestStop() {
stopRequested = true;
}
private static synchronized boolean stopRequested() {
return stopRequested;
}
public static void main(String[] args)
throws InterruptedException {
Thread backgroundThread = new Thread(() -> {
int i = 0;
while (!stopRequested())
i++;
});
backgroundThread.start();
TimeUnit.SECONDS.sleep(1);
requestStop();
}
}
Note:
Both the write method (requestStop) and the read method (stop-
Requested) are synchronized. It is not sufficient to synchronize only the write method! SYNCHRONIZATION is NOT GUARENTEED to WORK UNLESS both READ and WRITE operations are SYNCHRONIZED.
Even without syncronization the “StopThread” Class Methods would be atomic.
In other Words – the synchronization on these methods is used solely for its communication effects, not for mutual exclusion.
Alternative to synchronization for communication effect :
Though Cost of synchronizing on each iteration of the loop is SMALL, There is a
CORRECT ALTERNATIVE that is LESS VERBOSE and whose PERFORMANCE is LIKELY to be
BETTER.
Solution 2:Use of volatile.
While the volatile modifier performs no mutual exclusion,
it guarantees that any thread that reads the field will see the most recently written value:
// Cooperative thread termination with a volatile field
public class StopThread {
private static volatile boolean stopRequested;
public static void main(String[] args)
throws InterruptedException {
Thread backgroundThread = new Thread(() -> {
int i = 0;
while (!stopRequested)
i++;
});
backgroundThread.start();
TimeUnit.SECONDS.sleep(1);
stopRequested = true;
}
}
Note:The locking in the second version of StopThread can be omitted if
stopRequested is declared volatile.
CAUTION!!! while using volatile keyword:
Example : // Broken – requires synchronization!
private static volatile int nextSerialNumber = 0;
public static int generateSerialNumber() {
return nextSerialNumber++;
}
The intent of the method is to guarantee that every invocation returns a unique
value (so long as there are no more than 232 invocations).
The method’s state consists of a single atomically accessible field, nextSerialNumber
and ALL POSSIBLE VALUES of this field are legal. Therefore, no synchronization is necessary to protect
its invariants
The Loop Hole :
The problem is that the increment operator (++) is not atomic.
It performs two operations on the nextSerialNumber field:
first it reads the value, and then it writes back a new value, equal to the old value plus one.
Internal Working How it fails if two threads access it:
If a second thread reads the field between the time a thread reads the old value and writes back a new one,
the second thread will see the same value as the first and return the same serial
number. This is a SAFETY FAILURE: the program computes the wrong results.
Solution 1 : for volatile SAFETY FAILURE-
One way to fix generateSerialNumber is to add the synchronized modifier
to its declaration. THIS ensures each invocation of the method will see the effects of all previous invocations.
To bulletproof the method, use long instead of int, or throw an exception if nextSerialNumber is about to wrap.
Solution 2:
As mentioned in Rule : Know & Use the libraby from “General programming” :
Use the class AtomicLong which is part of java.util.concurrent.atomic.
This package provides primitives for lock-free, thread-safe programming on single variables. While volatile provides only the communication effects of synchronization, this package also provides
atomicity.
// Lock-free synchronization with java.util.concurrent.atomic
private static final AtomicLong nextSerialNum = new AtomicLong();
public static long generateSerialNumber() {
return nextSerialNum.getAndIncrement();
}
Instead of doing all decoration follow base line wherever possible:
The best way to avoid the problems discussed in this item is not to share mutable data. Either share immutable data (Item 17) or don’t share at all. In other words, confine mutable data to a single thread.
Motivation for Brian Goetz to read: SAFE PUBLICATION
It is also important to have a deep understanding of the frameworks and libraries
you’re using BECAUSE they MAY INTRODUCE threads that you are UNAWARE OF.
It is acceptable for one thread to modify a data object for a while and then to share it with other threads, synchronizing only the act of sharing the object reference. Other threads can then read the object without further synchronization,
so long as it isn’t modified again. Such objects are said to be effectively immutable
[Goetz06, 3.5.4]. Transferring such an object reference from one thread to others is called safe publication[Goetz06, 3.5.3].
There are many ways to safely publish an object reference:
1.you can store it in a static field as part of class initialization;
2.you can store it in a volatile field, a final field, or
3.a field that is accessed with normal locking; or
- you can put it into a concurrent COLLECTOIN as mentioned in Rule “Prefer concurrency utilities to wait and notify” of this UNIT