Thursday, January 13, 2011

synchronized vs custom locking: Java synchronization performance

If you have paid close attention to locking and synchronization mechanisms in Java, you must have noticed that there were better locks introduced in Java 1.5. It was basically Doug Lea's concurrent programming library that was standardized as JSR 166. The most notable introduction of the ReentrantLock. This gives developers a finer control over locking than synchronized keyword. synchronized by default is an exclusive lock and for classic cases such as Reader-Writer problem, it performs very poorly. The Java code segment at the end of this note tests the performance difference between the two locking mechanisms. Below are the results of this test

NO_LOCK operations/second 403,348,600

RE_ENTRANT_LOCK operations/second 24,345,620
SYNCHRONIZED operations/second 5,340,017
NO_LOCK operations/second 518,446,185
RE_ENTRANT_LOCK operations/second 23,242,504
SYNCHRONIZED operations/second 5,270,688
NO_LOCK operations/second 541,463,736
RE_ENTRANT_LOCK operations/second 23,193,530
SYNCHRONIZED operations/second 5,448,608

A quick glance at the results will tell you that ReentrantLock is at least 5 times faster than the exclusive synchronization. You might also conclude that you should ditch synchronized altogether. But it must be understood that taking control of locking, you ought to be very careful with the way you use it. The first thing to remember is to use try...finally block even if you don't intend to capture any exception. Also, it is a good idea to refresh your ideas about Mutexes, Semaphores and Thread Monitors. Another advantage of using custom locks is when you need finer control over the order in which locks can be acquired and released.

Above tests were performed with Oracle Java SDK 1.6.0_2. However, according to this article on Sun employee's blog both synchronized keyword and custom locking should be equally performant with the modern JVMs. The article gives insight on implementation details of synchronization in Java

LockPerformanceCheck.java

import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;

public class LockPerformanceCheck implements Runnable {
enum Mode implements Runnable {
NO_LOCK {
public void run() {
operation();
}
},

RE_ENTRANT_LOCK {
private final Lock lock = new ReentrantLock();

public void run() {
lock.lock();
try {
operation();
} finally {
lock.unlock();
}
}
},

SYNCHRONIZED {
public synchronized void run() {
operation();
}
}
}

private final Mode mode;
private final int count;

public LockPerformanceCheck(Mode mode, int count) {
this.mode = mode;
this.count = count;
}

public void run() {
for (int i = 0; i < count; i++)
mode.run();
}

public static void operation() {
int a = 2 * 2;
}

private static void test(Mode mode) throws InterruptedException {
int threadNumber = 4;
int count = 1000 * 1000;
long start = System.nanoTime();
Thread[] threads = new Thread[threadNumber];
for (int i = 0; i < threadNumber; i++)
(threads[i] = new Thread(new LockPerformanceCheck(mode, count)))
.start();
for (int i = 0; i < threadNumber; i++)
threads[i].join();
long rate = 1000L * 1000 * 1000 * count * threadNumber
/ (System.nanoTime() - start);
System.out.printf("%s operations/second %,d%n", mode.toString(), rate);
}

public static void main(String[] args) throws InterruptedException {
for (int i = 0; i < 3; i++) {
test(Mode.NO_LOCK);
test(Mode.RE_ENTRANT_LOCK);
test(Mode.SYNCHRONIZED);
}
}
}
Sphere: Related Content