ReentrantLock.lock() doesn't block other threa

2019-07-07 05:33发布

问题:

I'm having a hard time understanding the behavior of ReentrantLock.lock()

I have the following class

import java.util.concurrent.locks.*;

class BlockingClass {

    private Lock lock = new ReentrantLock();

    private Condition condition = lock.newCondition();

    public void a() {
        lock.lock();
        System.out.println("called in method a(): lock()");

        try {
            System.out.println("called in method a(): await()");
            condition.await();
        } 
        catch (InterruptedException e) {} 
        finally {
            lock.unlock();
            System.out.println("called in method a(): unlock() ");
        }
    }

    public void b() {
        lock.lock();
        System.out.println("called in method b(): lock()");

        System.out.println("called in method b(): signalAll()");
        condition.signalAll();

        lock.unlock();
        System.out.println("called in method b(): unlock() ");
    }
}

which I run with the following test:

class BlockingClassTest {
    public static void main(String[] args) throws InterruptedException {

        final BlockingClass blockingClass = new BlockingClass();

        new Thread() {
            public void run() {
                System.out.println("Thread1 calling a()");
                blockingClass.a();
            }
        }.start();

        Thread.sleep(1000); 

        new Thread() {
            public void run() {
                System.out.println("Thread2 calling b()");
                blockingClass.b();
            }
        }.start();
    }
}

I would have expected a deadlock. Once the a() method calls lock.lock(), I would expect that anyone calling the b() method would have to wait at b's lock.lock() until the thread calling a() had called lock.unlock(). But since a() is waiting for b() to call condition.signalAll(), both methods should have stayed blocked forever.

Instead, this is the output I get in the console:

Thread1 calling a()
called in method a(): lock()
called in method a(): await()
Thread2 calling b()
called in method b(): lock()
called in method b(): signalAll()
called in method a(): unlock() 
called in method b(): unlock() 

What am I misunderstanding about the proper use and functioning of lock() and unlock()?

回答1:

You're not misunderstanding ReentrantLock, you're misunderstanding Condition. A Condition is bound to a lock and Condition.await() will effectively unlock, check and wait, and then relock the lock. See Condition.await().

In a(), between lock() and the call to await(), and between the return of await() and unlock(), your lock is behaving as you expect. Inside the call to await(), the Condition is managing it.

This is part of the general concept of a "condition variable"; it's why any thread library you find associates a lock of some sort with a condition (e.g. in POSIX C, pthread_cond_wait requires both a condition variable and a mutex).

Check out the Wikipedia article on condition variables, it explains this behavior and the reasons for it in detail.



回答2:

Your call to condition.await(); will release the lock, leaving the thread in a wait state, so thread b can just acquire the lock.

Your a() method will continue to run once b() has released it's lock, since you signaled the condition.



回答3:

The answer has been given but I thought I'd just quote the javadocs for Condition.await() to provide more context:

Causes the current thread to wait until it is signalled or interrupted.

The lock associated with this Condition is atomically released and the current thread becomes disabled for thread scheduling purposes and lies dormant until one of four things happens:

  1. Some other thread invokes the signal method for this Condition and the current thread happens to be chosen as the thread to be awakened; or
  2. Some other thread invokes the signalAll method for this Condition; or
  3. Some other thread interrupts the current thread, and interruption of thread suspension is supported; or
  4. A "spurious wakeup" occurs.

In all cases, before this method can return the current thread must re-acquire the lock associated with this condition. When the thread returns it is guaranteed to hold this lock.

So when you call condition.await() it releases the lock allowing the other thread to enter the locked section. This is the same behavior as Object.wait() when inside of a synchronized code block.