javamultithreadingwaitnotify

Notify does not work in the unlock method. Elements that are included in the wait, do not come out of waiting


I'm just starting to learn multithreading.

Notify does not work in the unlock method. Elements that are included in the wait, do not come out of waiting.

public class Tunnel {
    static AtomicInteger limit = new AtomicInteger(0);
    static boolean isOpen = false;
    void goIntoTunnel() throws InterruptedException {
        lock();
        int timeIntoTunnel = (int) (Math.random() * 5000);
        System.out.println(Thread.currentThread().getName() + " go into the tunnel");
        Thread.sleep(timeIntoTunnel);
        unlock();
        System.out.println(Thread.currentThread().getName() + " left the tunnel, time: " + timeIntoTunnel);
    }

    void lock() throws InterruptedException {
        synchronized (this) {
            if (limit.get() < 3) {
                limit.incrementAndGet();
            } else isOpen = true;
            while (isOpen) {
                wait();
            }
        }
    }

    void unlock() throws InterruptedException {
        synchronized (this) {
            limit.getAndSet(limit.get()-1);
            isOpen = false;
                notify();
        }
    }
}

Simulation of the race. Cars enter the tunnel, and there cannot be more than 3 cars in the tunnel at the same time


Solution

  • Notify does not work in the unlock method. Elements that are included in the wait, do not come out of waiting.

    Your code works fine on my machine.

    The testing code:

    public class App {
      
      public static void main(String[] args) throws Throwable {
        var numTasks = 10;
        var tunnel = new Tunnel();
        var startLatch = new CountDownLatch(numTasks);
        var threads = IntStream.rangeClosed(1, numTasks).mapToObj(i -> {
          var t = new Thread(() -> {
            try {
              startLatch.countDown();
              startLatch.await();
              tunnel.goIntoTunnel();
            } catch (InterruptedException e) {
              e.printStackTrace();
            }
          });
          t.setName("Car" + i);
          return t;
        }).toList();
        threads.forEach(t -> t.start());
        for (var t: threads) {
          t.join();
        }
      }
    }
    

    The output:

    Car1 go into the tunnel
    Car3 go into the tunnel
    Car2 go into the tunnel
    Car4 go into the tunnel
    Car1 left the tunnel, time: 327
    Car4 left the tunnel, time: 2372
    Car5 go into the tunnel
    Car3 left the tunnel, time: 4174
    Car6 go into the tunnel
    Car2 left the tunnel, time: 4661
    Car8 go into the tunnel
    Car5 left the tunnel, time: 1995
    Car7 go into the tunnel
    Car8 left the tunnel, time: 2323
    Car9 go into the tunnel
    Car7 left the tunnel, time: 2794
    Car10 go into the tunnel
    Car6 left the tunnel, time: 3927
    Car9 left the tunnel, time: 3367
    Car10 left the tunnel, time: 4590
    

    I don't see anything in the code of Tunnel that might prevent waiting threads from waking up:

    1. every car-thread calls notify() in the end, this wakes up one waiting thread (if there are any)
    2. the awoken thread also call notify(), this wakes up another waiting thread
    3. ...

    The only thing I can think of is if this Tunnel object is also used somewhere else in your code for waiting.
    In this case notify() in unlock() might wake up that other waiting thread instead of one of the waiting car-threads.

    P.S. There are some additional problems in your code:

    1. limit is not incremented inside lock() when the thread is awoken. This might lead to 4 cars (3 threads that didn't wait + 1 awoken thread) in the tunnel simultaneously.
    2. Tunnel.isOpen is true when the tunnel is closed, and is false when the tunnel is closed. That's counter-intuitive.
    3. You can replace AtomicInteger by int, because in your code it's used only inside synchronized blocks. And code like limit.getAndSet(limit.get()-1) defeats the purpose of the AtomicInteger's existence.