Thread-safety guarantees that no two threads can simultaneously access or modify some shared data. However, if two or more operations need to be performed safely, it becomes necessary to enforce atomicity. It is possible for two threads to read some shared value, independently perform operations on it and induce a race condition while storing the final result. For example, programmers
Programmers sometimes assume that using a thread-safe Collection
does not require explicit synchronization which is a misleading thought. It follows that using a thread-safe Collection
by itself may does not ensure program correctness unless special care is taken to ensure that the client performs all related and independently atomic operations, as one atomic operation.
Noncompliant Code Example
This noncompliant code example is comprised of comprises an ArrayList
collection which is non-thread-safe by default. There is, however, a way around this drawback. Most thread-unsafe classes have a synchronized thread-safe version, for example, Collections.synchronizedList
is a good substitute for ArrayList
and Collections.synchronizedMap
is a good alternative to HashMap
. The atomicity pitfall described in the coming lines, remains to be addressed even when the particular Collection
offers thread-safety benefits.
...
The operations within a thread's run()
method are non-atomic. That is, it is possible for the first thread to operate on data that it does not expect. This noncompliant code's output, consisting of varying array lengths, indicates a race condition between threads. In other words, the statements that are responsible for adding an IP address and printing it out are not sequentially consistent.
...
Compliant Solution
Composition offers some more benefits as compared to the previous solution, although at the cost of a slight performance penalty (refer to OBJ07-J. Understand how a superclass can affect a subclass for details on how to implement composition). This allows the CompositeCollection
class to use its own intrinsic lock in a way that is completely independent of the lock of the underlying list class.
Code Block | ||
---|---|---|
| ||
Code Block | ||
| ||
class CompositeCollection implements Runnable { private List<InetAddress> ips; public CompositeCollection(List<InetAddress> list) { this.ips = list; } public synchronized void addIPAddress(InetAddress ia) { // Validate ips.add(ia); } public void run() { try { InetAddress[] ia; ia = (InetAddress[]) ips.toArray(new InetAddress[0]); System.out.println("Number of IPs: " + ia.length); } catch (UnknownHostException e) { /* Forward to handler */ } } } |
Wiki Markup |
---|
This approach allows the {{CompositeCollection}} class to use its own intrinsic lock in a way that is completely independent of the lock of the underlying list class. Moreover, this permits the underlying collection to be thread-unsafe because the {{CompositeCollection}} wrapper prevents direct accesses to its methods by exposing its own synchronized equivalents. This approach also provides consistent locking even when the underlying list is not thread-safe or when it changes its locking policy. \[[Goetz 06|AA. Java References#Goetz 06]\] |
...