In Java, there is a built-in language-level locking mechanism: the synchronized
block, which can use any Java object as an intrinsic lock (i.e. every Java object may have a monitor associated with it).
Intrinsic locks provide atomicity to groups of statements. To understand what that means for us, let's have a look at an example where synchronized
is useful:
private static int t = 0;
private static Object mutex = new Object();
public static void main(String[] args) {
ExecutorService executorService = Executors.newFixedThreadPool(400); // The high thread count is for demonstration purposes.
for (int i = 0; i < 100; i++) {
executorService.execute(() -> {
synchronized (mutex) {
t++;
System.out.println(MessageFormat.format("t: {0}", t));
}
});
}
executorService.shutdown();
}
In this case, if it weren't for the synchronized
block, there would have been multiple concurrency issues involved. The first one would be with the post increment operator (it isn't atomic in itself), and the second would be that we would be observing the value of t after an arbitrary amount of other threads has had the chance to modify it. However, since we acquired an intrinsic lock, there will be no race conditions here and the output will contain numbers from 1 to 100 in their normal order.
Intrinsic locks in Java are mutexes (i.e. mutual execution locks). Mutual execution means that if one thread has acquired the lock, the second will be forced to wait for the first one to release it before it can acquire the lock for itself. Note: An operation that may put the thread into the wait (sleep) state is called a blocking operation. Thus, acquiring a lock is a blocking operation.
Intrinsic locks in Java are reentrant. This means that if a thread attempts to acquire a lock it already owns, it will not block and it will successfully acquire it. For instance, the following code will not block when called:
public void bar(){
synchronized(this){
...
}
}
public void foo(){
synchronized(this){
bar();
}
}
Beside synchronized
blocks, there are also synchronized
methods.
The following blocks of code are practically equivalent (even though the bytecode seems to be different):
synchronized
block on this
:
public void foo() {
synchronized(this) {
doStuff();
}
}
synchronized
method:
public synchronized void foo() {
doStuff();
}
Likewise for static
methods, this:
class MyClass {
...
public static void bar() {
synchronized(MyClass.class) {
doSomeOtherStuff();
}
}
}
has the same effect as this:
class MyClass {
...
public static synchronized void bar() {
doSomeOtherStuff();
}
}