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
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:
notify()
in the end, this wakes up one waiting thread (if there are any)notify()
, this wakes up another waiting threadThe 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:
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.Tunnel.isOpen
is true
when the tunnel is closed, and is false
when the tunnel is closed. That's counter-intuitive.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.