The "synchronized" Keyword
The reserved keyword synchronized is used to define blocks of code and methods that represent critical sections/regions.
public class MyConcurrentArray<T> {
private static int numberOfInstances = 0;
private T[] content;
public MyConcurrentArray(int size) {
if (size > 0) {
content = new T[size];
} else {
throw new RuntimeException("Negative size provided for MyConcurrentArray instantiation.");
}
synchronized(MyConcurrentArray.class) {
++numberOfInstances;
}
}
// Synchronized method.
public synchronized T get(int index) {
if (index < content.length) {
return content[index];
}
throw new IndexOutOfBoundsException(index + " is out of bounds for MyConcurrentArray of size " + content.length);
}
public void set(int index, T newT) {
// Synchronized code block using the current instance (this) as a lock.
synchronized(this) {
if (index < content.length) {
content[index] = newT;
}
throw new IndexOutOfBoundsException(index + " is out of bounds for MyConcurrentArray of size " + content.length);
}
}
// Static synchronized method.
public static synchronized int getNumberOfInstances(){
return numberOfInstances;
}
public void size() {
return content.length;
}
}
In discussing the behavior of synchronized code blocks, let's use the example above where a class is defined to implement the concept of a fixed-size vector that can be used in a multithreading program (a thread-safe data structure).
Note that the get method is defined as synchronized. When a thread calls this method on an instance of the MyConcurrentArray class, it first needs to obtain the monitor associated with this object in order to execute the method body. If the monitor is not held by any other thread, the calling thread can execute the method's instructions. Otherwise, it will be blocked (waiting) until the monitor becomes available. After executing the method body, the method releases access to the monitor.
In the case of the set method, there is a synchronized block of instructions. It uses the monitor designated within the parentheses to provide exclusive access to the current thread within the critical region. In our example, it utilizes the current instance of the object (this). The mechanism for entering and exiting the critical section is the same as described above for synchronized methods.
For synchronized static methods, an attempt is made to obtain the monitor associated with the class to execute their code. This happens because a static method belongs to the class, not to any instance of the class. Therefore, when exclusive access to a static field in a class is needed, the class is used in the header of the synchronized block (e.g., MyClass.class
as presented in the constructor of the example).
Synchronized methods and code blocks in Java are reentrant. If a thread has acquired the monitor of an object, it can enter any other synchronized block or method associated with that object (implicitly with that monitor). This behavior is not activated by default for pthread_mutex_t defined in C (it can be achieved by specifying attributes at creation: PTHREAD_MUTEX_RECURSIVE).