views:

385

answers:

3

In Java, do ReentrantLock.lock() and ReetrantLock.unlock() use the same locking mechanism as synchronized()?

My guess is "No," but I'm hoping to be wrong.

Example:

Imagine that Thread 1 and Thread 2 both have access to:

ReentrantLock lock = new ReentrantLock();

Thread 1 runs:

synchronized (lock) {
    // blah
}

Thread 2 runs:

lock.lock();
try {
    // blah
}
finally {
    lock.unlock();
}

Assume Thread 1 reaches its part first, then Thread 2 before Thread 1 is finished: will Thread 2 wait for Thread 1 to leave the synchronized() block, or will it go ahead and run?

+6  A: 

No, Thread 2 can lock() even when Thread 1 is synchronized on the same lock. This is what the documentation has to say:

Note that Lock instances are just normal objects and can themselves be used as the target in a synchronized statement. Acquiring the monitor lock of a Lock instance has no specified relationship with invoking any of the lock() methods of that instance. It is recommended that to avoid confusion you never use Lock instances in this way, except within their own implementation.

erickson
That's what I figured. There's one place where I need to use `ReetrantLock.tryLock()`, and I was hoping to get away with `synchronized` everywhere else. Thanks.
yarvin
+2  A: 

The two mechanisms are different. Implementation/performance wise:

  • the synchronized mechanism uses a locking mechanism that is "built into" the JVM; the underlying mechanism is subject to the particular JVM implementation, but typically uses a combination of a raw compare-and-set operation (CAS) instruction for cases where the lock isn't contended plus underlying locking mechanisms provided by the OS;
  • the lock classes such as ReentrantLock are basically coded in pure Java (via a library introduced in Java 5 which exposes CAS instructions and thread descheduling to Java) and so is somewhat more standardised across OS's and more controllable (see below).

Under some circumstances, the explicit locks can perform better. If you look at this comparison of locking mechanisms I performed under Java 5, you'll see that in that particular test (multiple threads accessing an array), explicit lock classes configured in "unfair" mode (the yellow and cyan triangles) allow more throughput than plain synchronized (the purple arrows).

(I should also say that the performance of synchronized has been improved in more recent versions of Hotspot; there may not be much in it on the latest versions or indeed under other circumstances-- this is obviously one test in one environment.)

Functionality-wise:

  • the synchronized mechanism provides minimal functionality (you can lock and unlock, locking is an all-or-nothing operation, you're more subject to the algorithm the OS writers decided on), though with the advantage of built-in syntax and some monitoring built into the JVM;
  • the explicit lock classes provide more control, notably you can specify a "fair" lock, lock with a timeout, override if you need to alter the lock's behiour...
Neil Coffey
A: 

Hi!!

It seems that the ReentrantLock is not working. I have been working around this little example and something is missing here. I made this little test application that simulate a transaction concurrent scenario over an account. As I a can see, there is a error on the lock because if you run it, and you wait for a while you will see that the account balance is going to have a negative balance. This shouldn't happen if the lock were made.

I hope I have made a mistake.

this is the code.

public class TestMain {

public static void main(String[] args) {

    final ArrayBlockingQueue<Long> queue = new ArrayBlockingQueue<Long>(10);

    int nThreads = Integer.parseInt(args[0]);
    for (int i = 0; i < nThreads; i++)
        new BankSystem(queue);

    for (int i = 0; i < 20; i++)
        new Thread(new WithdrawRequests(queue)).start();

    for (int i = 0; i < 10; i++)
        new Thread(new DepositRequests(queue)).start();

}

}

public class BankSystem implements Runnable {

private Thread tread;
private BlockingQueue<Long> queue;
private ProcessOperation processOperation = new ProcessOperation();

public BankSystem(BlockingQueue<Long> q) {
    queue = q;
    tread = new Thread(this);
    tread.start();
}

public void run() {
    long request, result;
    try {
        while (true) {
            request = queue.take().longValue();
            result = processOperation.process(request);
            System.out.println("Calculated result of " + result + " from " + request);
        }
    } catch (InterruptedException ex) {

    }
}

}

public class ProcessOperation {

private Account account = new Account(200);

public long process(long request) {
    return account.transfer(request);
}

}

public class Account {

private final ReentrantLock lock = new ReentrantLock(true);

private static long balance = 0;

public Account(long initialBalance) {
    this.balance = initialBalance;
}

// synchronized
public long transfer(long amount) {
    lock.lock();
    try {
        if ((balance < 0) || (balance > 40000))
            System.out.println("ERROR!!! (" + balance + ")");

        if ((balance + amount) < 0)
            return balance;

        return balance += amount;

    } finally {
        lock.unlock();
    }
}

public long getBalance() {
    return balance;
}

}

public class WithdrawRequests implements Runnable {

private Thread thread;
private BlockingQueue<Long> queue;

public WithdrawRequests(BlockingQueue<Long> q) {
    queue = q;
    thread = new Thread(this);
    thread.start();
}

public void run() {
    try {
        Random randomGenerator = new Random();
        while (true) {
            int amount = randomGenerator.nextInt(300);
            queue.put(new Long(amount*-1));
            Thread.yield();
        }

    } catch (InterruptedException ex) {
        ex.printStackTrace();
    }
}

}

public class DepositRequests implements Runnable { private Thread tread; private BlockingQueue queue;

public DepositRequests(BlockingQueue<Long> q) {
    queue = q;
    tread = new Thread(this);
    tread.start();
}

public void run() {
    try {
        Random randomGenerator = new Random();
        while (true) {
            int amount = randomGenerator.nextInt(100);          
            queue.put(new Long(amount));
            Thread.yield();
        }

    } catch (InterruptedException ex) {
        ex.printStackTrace();
    }
}

}

However, if you make the transfer method of the Account class "synchronized", you will not have an error on the account balance.

Do you have any idea?

Cheers!!

Andres Navarro