Versions Compared

Key

  • This line was added.
  • This line was removed.
  • Formatting was changed.
Comment: temporary commit

A consistent locking policy guarantees that multiple threads cannot simultaneously access or modify shared data. In the absence of such a policy, it is possible to introduce a race condition. If two or more operations need to be performed as a single atomic operation, it is necessary to implement a consistent locking policy by either using intrinsic synchronization or the java.util.concurrent utilities. In the absence of such a policy, the code is susceptible to race conditions.

Given an invariant involving multiple objects, a programmer may incorrectly assume that individually atomic operations require no additional locking; however, this is not the case. Similarly, programmers may incorrectly assume that using a thread-safe Collection does not require explicit synchronization to preserve an invariant that involves the collection's elements. A thread-safe class can only guarantee atomicity of its individual methods. A grouping of calls to such methods requires additional synchronization.

...

Enumerations and iterators also require explicit synchronization on the collection object (client-side locking) or an internal a private final lock object.

Compound operations on shared variables are also non-atomic. See CON01-J. Ensure that compound operations on shared variables are atomic for more information.

...

Prefer using block synchronization instead of method synchronization when the method contains non-atomic operations that either do not require any synchronization or can use a more fine-grained locking scheme involving multiple internal private final lock objects. Non-atomic operations can be decoupled from those that require synchronization and executed outside the synchronized block. The guideline CON04-J. Synchronize using a private final lock object has more details on using private internal final lock objects and block synchronization.

...

This noncompliant code example has uses a java.util.ArrayList<E> collection which is not thread-safe by default. However, the Collections.synchronizedList is used as a synchronization wrapper for ArrayList.

Code Block
bgColor#FFCCCC
final class IPHolder {
  private final List<InetAddress> ips = Collections.synchronizedList(new ArrayList<InetAddress>());
  
  public void addIPAddress(InetAddress address) {
    // Validate address
    ips.add(address);
  }
  
  public void addAndPrintIPaddAndPrintIPAddresses(InetAddress address) {
    addIPAddress(address);
    InetAddress[] ia = (InetAddress[]) ips.toArray(new InetAddress[0]);      
    System.out.println("Number of IPs: " + ia.length);     // Iterate through array ia ...
  }
}

Even though the Collection wrapper offers thread-safety guarantees, atomicity related issues manifest themselves when calling methods of the this class. When the addAndPrintIPaddAndPrintIPAddresses() method is invoked on the same object from multiple threads, the output which consists of varying array lengths, may indicate a race condition between the threadsdifferent sequences of IP addresses are produced from every other thread, which indicates that the code is operating under race conditions. The statements in method addAndPrintIP() that are responsible for adding an IP address and printing out the length of the list, addAndPrintIPAddresses() are not sequentially consistent.

Compliant Solution (Synchronized block)

To eliminate the race conditionconditions, ensure atomicity by using the underlying list's lock. This can be achieved by including compliant solution includes all statements that use the array list within a synchronized block that locks on the list.

Code Block
bgColor#ccccff
final class IPHolder {
  private final List<InetAddress> ips = Collections.synchronizedList(new ArrayList<InetAddress>());

  public void addIPAddress(InetAddress address) { 
    synchronized (ips) { 
      // Validate
      ips.add(address);
    }
  }

  public void addAndPrintIPaddAndPrintIPAddresses(InetAddress address) {
    synchronized (ips) {
      addIPAddress(address);
      InetAddress[] ia = (InetAddress[]) ips.toArray(new InetAddress[0]);           
      System.out.println("Number of IPs: " + ia.length); // Iterate through array ia ...
    }
  }
}

Wiki Markup
This technique is also called client-side locking \[[Goetz 06|AA. Java References#Goetz 06]\], because the class holds a lock on an object that might, presumably, might be accessible to other classes. Client-side locking is not always an appropriate strategy; see [CON31-J. Avoid client-side locking when using classes that do not commit to their locking strategy] for more information. 

...

This code does not violate CON40-J. Do not synchronize on a collection view if the backing collection is accessible, because while it does synchronize on a collectoin collection view (the synchronizedList), the backing collection is not accessible, and hence cannot be modified by any code.

...

Code Block
bgColor#FFCCCC
final class KeyedCounter {
  private final Map<String, Integer> map = new HashMap<String, Integer>(); 
  private final Object lock = new Object();

  public void increment(String key) {
    synchronized (lock) {
      Integer old = map.get(key);
      int value = (old == null) ? 1 : old.intValue() + 1;
      map.put(key, value);
    }
  }

  public Integer getCount(String key) {
    synchronized (lock) {
      return map.get(key);
    }
  }
}

Because there is not the check for integer overflow following the addition is absent, the caller must ensure that the increment() method is called no more than Integer.MAX_VALUE times for any key. Refer to INT00-J. Perform explicit range checking to ensure integer operations do not overflow for more information.

...

To ensure atomicity, this compliant solution uses an internal a private final lock object to synchronize the statements of the increment() and getCount() methods.

...