Threads often need to communicate with each other. For example, one thread may need to tell other threads that a task is done, or that a shared resource is available.
Java has a built-in mechanism for thread communication, called the java.util.concurrent.locks.Condition
class. In this article, we'll take a look at how to use Condition
to communicate between threads.
Condition is a Java class that allows threads to wait for or signal other threads. It's part of the java.util.concurrent.locks
package, which also contains the Lock
class.
Condition works with Lock to provide a more flexible way of controlling when threads can run. With Lock, a thread can only run when it holds the lock. With Condition, a thread can run only when another thread has signaled it.
Condition works with Lock to provide a more flexible way of controlling when threads can run. With Lock, a thread can only run when it holds the lock. With Condition, a thread can run only when another thread has signaled it.
Here's a simple example of how Condition can be used. Suppose we have a shared resource that can be in one of two states: empty or full. We have two threads: one that produces the resource, and one that consumes it.
We want to make sure that the consumer thread only tries to consume the resource when it's in the "full" state, and that the producer thread only tries to produce the resource when it's in the "empty" state. To do this, we can use a Lock and a Condition:
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
public class Resource {
private final Lock lock = new ReentrantLock();
private final Condition isEmpty = lock.newCondition();
private final Condition isFull = lock.newCondition();
private boolean empty = true;
public void produce() throws InterruptedException {
lock.lock();
try {
while (!empty) {
isEmpty.await();
}
// produce resource
empty = false;
isFull.signalAll();
} finally {
lock.unlock();
}
}
public void consume() throws InterruptedException {
lock.lock();
try {
while (empty) {
isFull.await();
}
// consume resource
empty = true;
isEmpty.signalAll();
} finally {
lock.unlock();
}
}
}
In this example, the produce()
method first checks if the resource is empty. If it's not, it waits until it is empty (by calling isEmpty.await()
). Once the resource is empty, it produces the resource and signals any waiting threads (by calling isFull.signalAll()
).
Similarly, the consume()
method first checks if the resource is full. If it's not, it waits until it is full (by calling isFull.await()
). Once the resource is full, it consumes the resource and signals any waiting threads (by calling isEmpty.signalAll()
).
Condition also provides a signal()
method, which wakes up a single thread that is waiting on the condition. If multiple threads are waiting, one of them will be chosen at random to wake up.
There is also a awaitUninterruptibly()
method, which is similar to await()
except that it doesn't throw InterruptedException if the thread is interrupted.
Here are a few tips for using Condition:
Always call await()
or signal()
within a loop. This is because await()
can be interrupted (by a call to interrupt()
), and signal()
can be lost (if multiple threads are waiting, only one of them will be woken up).
Be careful not to call await()
or signal()
outside of a lock. If you do, another thread could acquire the lock in between the time you check the condition and the time you call await()
or signal()
. This could cause your program to freeze.
When using multiple conditions, be careful not to use the same lock for all of them. This could cause a deadlock.
In this article, we've seen how to use Java's java.util.concurrent.locks.Condition
class for thread communication. We've also seen some tips for using Condition.