Wiki Markup |
---|
Code that uses synchronization can sometimes be enigmatic and tricky to debug. Misuse of synchronization primitives is a common source of implementation errors. An analysis of the JDK 1.6.0 source code unveiled at least 31 bugs that fell into this category. \[[Pugh 08|AA. Java References#Pugh 08]\] |
There are several oversights and programming errors associated with the improper use of locks, for example:
- The lock object might be accessible to hostile code that can acquire the lock and hold it indefinitely. Locks should be made inaccessible outside the package they are declared in.
- A non-final field referring to a lock object can be modified to refer to a different lock object. This can cause two critical sections of code that expect to lock on the same object to lock on different objects, or, two critical sections intending to lock on different objects to lock on the same shared object. This can result in unanticipated outcomes.
- An object that uses dynamic locking, such as through
java.util.concurrent.ReentrantLock
, should not expose its intrinsic lock so that other code can use it because this may cause confusion and introduce maintenance issues.
Noncompliant Code Example (public
nonfinal lock object)
This noncompliant code example locks on a public
nonfinal object.
Code Block | ||
---|---|---|
| ||
public Object publicLock = new Object();
private void doSomething() {
synchronized(publicLock) {
// body
}
}
|
It is possible for untrusted code to change the value of the lock object and foil all attempts to synchronize.
Noncompliant Code Example
...
This noncompliant code example synchronizes on a nonfinal field and demonstrates no mutual exclusion properties.
...
bgColor | #FFcccc |
---|
...
(
...
This is because the thread that holds a lock on the nonfinal field object can modify the field's value to refer to some other object. This might cause two threads that intend to lock on the same object to lock on different objects, enabling them to execute the two critical sections in an unsafe manner.
Compliant Solution (private
and final
lock object)
This compliant solution synchronizes using a lock object that is declared as private
and final
.
Code Block | ||
---|---|---|
| ||
private final Integer lock = new Integer(0);
private void doSomething() {
synchronized(lock) { /* ... */ }
}
// setValue() is disallowed
|
Noncompliant Code Example (Boolean
lock object)
This noncompliant code example uses a Boolean
field for synchronization.
Code Block | ||
---|---|---|
| ||
private final Boolean initialized = Boolean.FALSE;
synchronized(initialized) {
if (!initialized) {
// Perform initialization
initialized = Boolean.TRUE;
}
}
|
Wiki Markup |
---|
However, because the field is non-final, there There can be two possible valid values ({{true}} and {{false}} of the variable {{initialized}}, discounting {{null}}) that a {{Boolean}} can assume. Consequently, any other code that synchronizes on a {{Boolean}} variable with the same value, canmay cause unresponsiveness and deadlocks \[[Findbugs 08|AA. Java References#Findbugs 08]\]. Even if the field were final, the code would use the intrinsic lock of {{Boolean.FALSE}} or {{Boolean.TRUE}}, which are accessible throughout the program. |
Noncompliant Code Example (Boxed primitive)
...
Again, the class object being synchronized must not be accessible to hostile code, as discussed in the previous example.
Noncompliant Code Example (nonstatic lock object for static
data)
This noncompliant code example uses a nonstatic lock object to guard access to a static
field. If two Runnable
tasks, each consisting of a thread are started, they will create two instances of the lock object and lock on each separately.
Code Block | ||
---|---|---|
| ||
class CountBoxes implements Runnable {
static volatile int counter;
// ...
Object lock = new Object();
public void run() {
synchronized(lock) {
counter++;
// ...
}
}
public static void main(String[] args) {
Runnable r1 = new CountBoxes();
Thread t1 = new Thread(r1);
Runnable r2 = new CountBoxes();
Thread t2 = new Thread(r2);
t1.start();
t2.start();
}
}
|
This does not prevent either thread from observing an inconsistent value of counter
because the increment operation on volatile
fields is not atomic in the absence of proper synchronization.
Noncompliant Code Example (
...
This noncompliant code example uses method synchronization to protect access to a static
class member.
Code Block | ||
---|---|---|
| ||
class CountBoxes implements Runnable {
static volatile int counter;
// ...
public synchronized void run() {
counter++;
// ...
}
// ...
}
|
The problem is that this lock is associated with each instance of the class and not with the class object itself. Consequently, threads constructed using different Runnable
instances may observe inconsistent values of the counter
.
Compliant Solution (static
lock object)
This compliant solution declares the lock object as static
and consequently, ensures the atomicity of the increment operation.
Code Block | ||
---|---|---|
| ||
class CountBoxes implements Runnable {
static int counter;
// ...
private static final Object lock = new Object();
public void run() {
synchronized(lock) {
counter++;
// ...
}
// ...
}
|
There is no requirement of declaring the counter
variable as volatile
when synchronization is used.
Noncompliant Code Example (ReentrantLock
lock object)
...
This noncompliant code example synchronizes on the view of a synchronized map.
Code Block | ||
---|---|---|
| ||
private final Map<Integer, String> map = Collections.synchronizedMap(new HashMap<Integer, String>()); private final Set<Integer> set = map.keySet(); synchronized(set) { // Incorrectly synchronizes on set for(Integer k : set) { // Do something } } |
...