Chapter 04 - Design A Thread Safe Class

Monitor pattern

https://java-design-patterns.com/patterns/monitor/

Basically use synchronization to achieve thread safe

public class ThreadSafeClass {
	private Object lock1 = new Object();
	private Object lock2 = new Object();

	public void methodA() {
		synchronized(lock1) {
			//critical section A
		}
	}

	public void methodB() {
		synchronized(lock1) {
			//critical section B
		}
	}

	public void methodC() {
		synchronized(lock2) {
			//critical section C
		}
	}

	public void methodD() {
		synchronized(lock2) {
			//critical section D
		}
	}
}

Cirtical section here means only 1 thread can access at a time

Example: Tracking vehicle

@ThreadSafe
public class MonitorVehicleTracker {
    @GuardedBy("this")
    private final Map<String, MutablePoint> locations;

    public MonitorVehicleTracker(Map<String, MutablePoint> locations) {
        this.locations = deepCopy(locations);
    }

    public synchronized Map<String, MutablePoint> getLocations() {
        return deepCopy(locations);
    }

    public synchronized MutablePoint getLocation(String id) {
        MutablePoint loc = locations.get(id);
        return loc == null ? null : new MutablePoint(loc);
    }

    public synchronized void setLocation(String id, int x, int y) {
        MutablePoint loc = locations.get(id);
        if (loc == null)
            throw new IllegalArgumentException("No such ID: " + id);

        loc.x = x;
        loc.y = y;
    }

    private static Map<String, MutablePoint> deepCopy(Map<String, MutablePoint> m) {
        Map<String, MutablePoint> result = new HashMap<String, MutablePoint>();
        for (String id : m.keySet())
            result.put(id, new MutablePoint(m.get(id)));

        return Collections.unmodifiableMap(result);
    }
}
@NotThreadSafe
public class MutablePoint {
    public int x, y;
    public MutablePoint() { x = 0; y = 0 }
    public MutablePoint(MutablePoint p) {
        this.x = p.x;
        this.y = p.y;
    }
}

In here, we're all synchronised on this. Even though locations is not threadsafe.

Delegation

We can use already threadsafe classes to handle

@ThreadSafe
public class DelegatingVehicleTracker {
    private final ConcurrentMap<String, Point> locations;
    private final Map<String, Point> unmodifiableMap;

    public DelegatingVehicleTracker(Map<String, Point> points) {
        locations = new ConcurrentHashMap<String, Point>(points);
        unmodifiableMap = Collections.unmodifiableMap(locations);
    }

    public Map<String, Point> getLocations() {
        return unmodifiableMap;
    }

    public Point getLocation(String id) {
        return locations.get(id);
    }

    public void setLocation(String id, int x, int y) {
        if (locations.replace(id, new Point(x, y)) == null)
            throw new IllegalArgumentException("invalid vehicle name: " + id);
    }
}
@Immutable
public class Point {
    public final int x, y;
    public Point(int x, int y) {
        this.x = x;
        this.y = y;
    }
}

Note: Collections.unmodifiableMap() will return the read-only view of the map. However, if the original map update, this view is also updated with new values. However unmodifiableMap cannot guarantee safety if the value or key is modifiable and the user modify it directly. It simply guard puts operations

Independent State Variable

We can use delegation for 2 or more variables when the variables are independent of each other. Consider the following scenarios

public class VisualComponent {
    private final List<KeyListener> keyListeners = new CopyOnWriteArrayList<KeyListener>();
    private final List<MouseListener> mouseListeners = new CopyOnWriteArrayList<MouseListener>();

    public void addKeyListener(KeyListener listener) {
        keyListeners.add(listener);
    }

    public void addMouseListener(MouseListener listener) {
        mouseListeners.add(listener);
    }

    public void removeKeyListener(KeyListener listener) {
        keyListeners.remove(listener);
    }

    public void removeMouseListener(MouseListener listener) {
        mouseListeners.remove(listener);
    }
}

The keyListeners and mouseListeners are separated from each other. Therefore we dont need

When delegation fail

Delegation fail when there is a compound action. For example:

public class NumberRange {
    // INVARIANT: lower <= upper
    private final AtomicInteger lower = new AtomicInteger(0);
    private final AtomicInteger upper = new AtomicInteger(0);

    public void setLower(int i) {
        // Warning -- unsafe check-then-act
        if (i > upper.get())
            throw new IllegalArgumentException("can’t set lower to " + i + " > upper");

        lower.set(i);
    }

    public void setUpper(int i) {
        // Warning -- unsafe check-then-act
        if (i < lower.get())
            throw new IllegalArgumentException("can’t set upper to " + i + " < lower");
        upper.set(i);
    }

    public boolean isInRange(int i) {
        return (i >= lower.get() && i <= upper.get());
    }
}

For example in this case if someone called setLower(5) and setUpper(4) the same time, we might get (5,4).

Safe publishing

Consider the previous example of non-threadsafe MutablePoint

@NotThreadSafe
public class MutablePoint {
    public int x, y;
    public MutablePoint() { x = 0; y = 0 }
    public MutablePoint(MutablePoint p) {
        this.x = p.x;
        this.y = p.y;
    }
}

We can make this threadsafe by

@ThreadSafe  
public class SafeMutablePoint {  
    private int x, y;  
  
    private SafeMutablePoint(int[] points) {  
        this.x = points[0];  
        this.y = points[1];  
    }  
  
    public SafeMutablePoint(SafeMutablePoint point) {  // Copy constructor
        this(point.get());  
    }  
  
    public SafeMutablePoint(int x, int y) {  
        this.x = x;  
        this.y = y;  
    }  
  
    public synchronized int[] get() {  
        return new int[] {x, y};  
    }  
  
    public synchronized void set(int x, int y) {  
        this.x = x;  
        this.y = y;  
    }  
}

Note that in here, for get() we need to use synchronised as well, volatile on x,y is not enough. The reason is when reading x, y could be change by another set(x, y) therefore it could cause uncertainty.

To prevent this, we need to make sure that both x and y are being read by the same time only.

For the copy constructor here, instead of doing this(this.x, this.y), we use get() so that we return a snapshot of consistent both x and y. Doing this.x, this.y exposes a risk of one value might changes half way, for example y might get changed after populating this.x.

Adding functionality to thread-safe class

Easiest way to create a threadsafe class is inherit from already existing thread-safe class. When adding functionality to an existing thread safe class we have a few options:

  1. Adding another function.
    • This option requires you have access to the code. However it's the most preferrable option since the source code is centralised
  2. Extending from the class
    • This option is more limited since it requires that the class is exposed enough functionality for us to extend.
    • This also split the source code to multiple places therefore making it hard to store

Example extending the vector class of putIfAbsent

@ThreadSafe
public class ExtendedVector<E> extends Vector<E> {  
    public synchronized boolean putIfAbsent(E item) {  
        if (this.contains(item)) {  
            return false;  
        }  
  
        this.add(item);  
        return true;  
    }  
}

Client side locking

The above example demonstrate how to extend a class to add more support. However, it's not like we can extend in all scenario.

For example, we want to extend boolean putIfAbsent method for object that returns for Collections.synchronizedList(..). This is not possible since we don't know what the class type inside the .. since it could be any class.

An idea could be to have a helper method and synchronised on the same synchronised list.

@ThreadSafe  
public class ClientSideLocking<E> {  
    final List<E> list = Collections.synchronizedList(new ArrayList<E>());  
    // ... other methiods
    public boolean putIfAbsent(E item) {  
        synchronized (list) {  
            if (list.contains(item)) {  
                return false;  
            }  
  
            list.add(item);  
            return true;  
        }  
    }  
}

It's important to note that in here we must use synchronized (list) to synchronise on the right object. This technique is call client-side locking.

However this technique is very fagile since it's putting the lock on another object that's not even related to the current object.

Composition

public abstract class CompositionList<T> implements List<T> {  
    private final List<T> list;  
  
    public CompositionList(List<T> list) {  
        this.list = list;  
    }  
  
    public synchronized boolean putIfAbsent(T item) {  
        if (this.contains(item)) {  
            return false;  
        }  
  
        list.add(item);  
        return true;  
    }  
    // Other method  
}

This time, we implements the List<T> and add our own implementation of each method with our synchronisation.

With this implementation, we assume that from now on, the user will use CompositionList instead of their own list. This add more overhead however is a more preferred way since it's more maintainable.

Documenting Synchronisation policies

It's always recommended to document the synchronisation policies. This can be used with the following annotation:

  • @ThreadSafe
  • @NotThreadSafe
  • @GuardedBy("..")

Interpreting vague documentation

If the code base does not specify if a class is thread safe or not. We can put ourself in the author place and see if this code would be implemented with thread safety.

For example the class HttpSession would make sense to be used concurrently with other thread. Therefore it's highly possible that the class will be thread safe.