If I have a synchronized method and two threads are waiting to enter it they seem to enter the thread Last In First Executed. Is there a way to make this First In First Executed?
This is the unit test that I’m using:
package com.test.thread;
import org.apache.log4j.Logger;
import org.junit.Test;
public class ThreadTest {
private static final Logger log = Logger.getLogger(ThreadTest.class);
@Test
public void testThreading() throws InterruptedException {
Thread t1 = new Thread(new Runnable() {
public void run() { synchd("1"); }
});
Thread t2 = new Thread(new Runnable() {
public void run() { synchd("2"); }
});
Thread t3 = new Thread(new Runnable() {
public void run() { synchd("3"); }
});
t3.start();
Thread.sleep(5);
t1.start();
t2.start();
Thread.sleep(12000);
}
public static synchronized void synchd(String output) {
log.debug(output);
try {
Thread.sleep(3000);
} catch (InterruptedException e) {
// do nothing
}
}
}
The output for this is always 3, 2, 1, and I’d like to find a way for it to be 3, 1, 2.
With
java.util.concurrent.locks.ReentrantLock, you can specify a fairness policy that’s relevant here. Passingtrueto the referenced constructor requests that the lock “play fair.” What that means is a little vague in the documentation, but studying the documentation for the underlyingAbstractQueuedSynchronizertype and the implementation ofReentrantLock$FairSync(in the Sun JDK) gives additional hints:Now, it’s still possible—though highly unlikely—that this newly-queued thread will acquire the lock next when it’s next released by its current holder, if it by then happens to wind up as first in line due to predecessors being interrupted, but observe that even though the logical model for a condition queue is a set of waiting threads, in the aforementioned implementation it is in fact a queue (a CLH Queue, described in its application to the Java library in the paper The java.util.concurrent Synchronizer Framework). In the fair mode, only the first item in the queue will acquire the lock.
Obviously, two threads can race when trying to acquire the lock “at the same time.” With a fair lock, you can expect that if thread A arrives first and calls
Lock#lock(), and winds up having to wait because the lock is held by thread C, and later thread B arrives and callsLock#lock()while thread C still holds it, B will get in line behind the already-queued A, and once C releases the lock, A will get a chance before B to acquire it. See the implementation inAbstractQueuedSynchronizer$unparkSuccessor()for the specific walk forward from the CLH queue’s head toward its tail. A will be closer to the head than B.The documentation for
ReentrantLockwarns that even when operating in fair mode, it’s possible that threads already waiting on the lock will lose out to the thread currently holding the lock releasing it and acquiring it again. I think—but am not sure—that this can occur when the current thread wins at getting in line ahead of other threads that have not yet landed in the queue. Also, note the warning concerningReentrantLock#tryLock(); unlike the timedReentrantLock#tryLock(long, TimeUnit), the former does not honor the fairness policy.This survey makes some conclusions based on one implementation. In general, it’s safer to take Mr. Barousse’s view: the acquisition ordering is best thought of as being a random grab from a set of waiters. However, with a fairness policy studied in enough depth, you can see that there is some determinism to be had. It doesn’t come for free, though; note the warnings about decreases in throughput when barging is prohibited.