Locks and semaphores are foundational synchronization mechanisms in Java, designed to control access to shared resources in concurrent programming. Proper use of these constructs ensures thread safety, prevents data corruption, and manages resource contention efficiently.
What is a Lock in Java?
A lock provides exclusive access to a shared resource by allowing only one thread at a time to execute a critical section of code. The simplest form in Java is the intrinsic lock obtained by the synchronized
keyword, which guards methods or blocks. For more flexibility, Java’s java.util.concurrent.locks
package offers classes like ReentrantLock
that provide advanced features such as interruptible lock acquisition, timed waits, and fairness policies.
Using locks ensures that when multiple threads try to modify shared data, one thread gains exclusive control while others wait, thus preventing race conditions.
Example of a Lock (ReentrantLock):
import java.util.concurrent.locks.ReentrantLock;
public class Counter {
private int count = 0;
private final ReentrantLock lock = new ReentrantLock();
public void increment() {
lock.lock(); // acquire lock
try {
count++; // critical section
} finally {
lock.unlock(); // release lock
}
}
public int getCount() {
return count;
}
}
What is a Semaphore in Java?
A semaphore controls access based on a set number of permits, allowing a fixed number of threads to access a resource concurrently. Threads must acquire a permit before entering the critical section and release it afterward. If no permits are available, threads block until a permit becomes free. This model suits scenarios like connection pools or task throttling, where parallel access is limited rather than exclusive.
Example of a Semaphore:
import java.util.concurrent.Semaphore;
public class WorkerPool {
private final Semaphore semaphore;
public WorkerPool(int maxConcurrent) {
this.semaphore = new Semaphore(maxConcurrent);
}
public void performTask() throws InterruptedException {
semaphore.acquire(); // acquire permit
try {
// critical section
} finally {
semaphore.release(); // release permit
}
}
}
Comparing Locks and Semaphores
Aspect | Lock | Semaphore |
---|---|---|
Concurrency | Single thread access (exclusive) | Multiple threads up to a limit (concurrent) |
Use case | Mutual exclusion in critical sections | Limit concurrent resource usage |
API examples | synchronized , ReentrantLock |
Semaphore |
Complexity | Simpler, single ownership | More flexible, requires permit management |
Best Practices for Using Locks and Semaphores
- Always release locks or semaphore permits in a
finally
block to avoid deadlocks. - Use locks for strict mutual exclusion when only one thread should execute at a time.
- Use semaphores when allowing multiple threads limited concurrent access.
- Keep the critical section as short as possible to reduce contention.
- Avoid acquiring multiple locks or permits in inconsistent order to prevent deadlocks.
Mastering locks and semaphores is key to writing thread-safe Java applications that perform optimally in concurrent environments. By choosing the right synchronization mechanism, developers can effectively balance safety and parallelism to build scalable, reliable systems.
Recent Comments