why does it seem like two threads are accessing one lock in my code?

james :

I'm trying to learn java multithreading, and this is a simple leetcode concurrency problem (https://leetcode.com/problems/print-foobar-alternately/). I came up with the following code, but I don't understand why it works. The problem says one thread call foo and one thread call bar, and it should print "foobar" n times.

public class Foobar {
    int n;
    boolean hasFoo;
    Lock lock;
    Condition cond;
    public Foobar(int n) {
        this.n = n;
        hasFoo = false;
        lock = new ReentrantLock();
        cond = lock.newCondition();
    }

    public void foo(Runnable printFoo) throws InterruptedException {
        for (int i = 0; i < n; i++) {
            lock.lock();
            while (hasFoo) {
                cond.await();
            }
            printFoo.run();
            hasFoo = true;
            cond.signalAll();
            lock.unlock();
        }
    }

    public void bar(Runnable printBar) throws InterruptedException {
        for (int i = 0; i < n; i++) {
            lock.lock();
            while (!hasFoo) {
                cond.await();
            }
            printBar.run();
            hasFoo = false;
            cond.signalAll();
            lock.unlock();
        }
    }
}

It works, but I don't understand why. From my understanding, if the "bar" thread runs first and acquires the lock, it should wait and the "foo" thread will block in the lock.lock(); line, but it turns out they both enter the locked part. Please enlighten me where I misunderstand lock in java. Here is how I call these two methods.

        Foobar f = new Foobar(10);
        Runnable rf = () -> {
            try {
                f.foo(() -> System.out.println("foo"));
            } catch (Exception e) {
                e.printStackTrace();
            }
        };
        Runnable rp = () -> {
            try {
                f.bar(() -> System.out.println("bar"));
            } catch (Exception e) {
                e.printStackTrace();
            }
        };
        Thread t1 = new Thread(rf, "foo-thread");
        Thread t2 = new Thread(rp, "bar-thread");
        t1.start();
        t2.start();
Andrew Tobilko :

if the "bar" thread runs first and acquires the lock, it should wait ...

Wait for what? The "bar" thread is the first that acquired the lock, there is nothing to wait for. This thread will immediately proceed to the next statement, which is cond.await(); where the lock will be released1, and the thread will go to sleep.

In the meanwhile, the "foo" thread can acquire the lock2, and print its message, and notify others that its job is done, which, subsequently, unlocks3 the sleeping "bar".

... the "foo" thread will be blocked in the lock.lock();

Exactly.

but it turns out they both enter the locked part.

No. One entered, another is waiting to enter. It's a simple lock - only one thread can acquire it at a time (it's like an advanced version of the synchronized statement).


1 The lock associated with this Condition is atomically released and the current thread becomes disabled for thread scheduling purposes and lies dormant

https://docs.oracle.com/en/java/javase/12/docs/api/java.base/java/util/concurrent/locks/Condition.html#await()

2 Remember, it was blocked since it tried to acquire the lock shorty after the "bar" did it.

If the lock is not available then the current thread becomes disabled for thread scheduling purposes and lies dormant until the lock has been acquired.

https://docs.oracle.com/en/java/javase/12/docs/api/java.base/java/util/concurrent/locks/Lock.html#lock()

3 By signalling to all, which one of the four methods to awake others.

  1. Some other thread invokes the signalAll() method for this Condition;

https://docs.oracle.com/javase/7/docs/api/java/util/concurrent/locks/Condition.html#await()

Guess you like

Origin http://43.154.161.224:23101/article/api/json?id=129384&siteId=1