← Back to Advanced Java Course | Chapter 11: Advanced & Security | Lesson 9 of 19

Java Locks & Semaphores

ReentrantLock Basics

ReentrantLock is a more flexible, explicit alternative to the synchronized keyword. You must call lock() to acquire it and unlock() inside a finally block to release it, since unlike synchronized blocks, the JVM won't release a ReentrantLock for you automatically if an exception is thrown.

Example: ReentrantLock Basics

java
import java.util.concurrent.locks.ReentrantLock;
public class Main {
	static final ReentrantLock lock = new ReentrantLock();
	public static void main(String[] args) {
		lock.lock();
		try {
			System.out.println("Critical section");
		} finally {
			lock.unlock(); // must release manually, unlike synchronized
		}
	}
}

Non-Blocking Locks with tryLock()

tryLock() attempts to acquire a lock without blocking the calling thread -- it returns true immediately if the lock was free, or false right away if it wasn't, letting a thread decide to do something else instead of waiting indefinitely for a busy resource.

Example: Non-Blocking Locks with tryLock()

java
import java.util.concurrent.locks.ReentrantLock;
public class Main {
	static final ReentrantLock lock = new ReentrantLock();
	public static void main(String[] args) {
		if (lock.tryLock()) { // returns immediately instead of blocking
			try { System.out.println("Acquired the lock"); } finally { lock.unlock(); }
		} else {
			System.out.println("Lock was busy, doing something else");
		}
	}
}

ReadWriteLock

ReadWriteLock improves throughput by allowing any number of threads to read a shared resource simultaneously, while still guaranteeing that only one thread can hold the write lock at a time and that writes are exclusive of all reads -- ideal for data that's read far more often than it's modified.

Example: ReadWriteLock

java
import java.util.concurrent.locks.*;
public class Main {
	static final ReadWriteLock rwLock = new ReentrantReadWriteLock();
	public static void main(String[] args) {
		rwLock.readLock().lock(); // multiple readers allowed at once
		try { System.out.println("Reading"); } finally { rwLock.readLock().unlock(); }
		rwLock.writeLock().lock(); // exclusive of all reads and other writes
		try { System.out.println("Writing"); } finally { rwLock.writeLock().unlock(); }
	}
}

Semaphore Basics

A Semaphore controls access to a limited pool of a resource using permits: threads call acquire() to take a permit before proceeding and release() to give it back afterward, which is the standard way to cap how many threads can use something concurrently, like a fixed-size connection pool.

Example: Semaphore Basics

java
import java.util.concurrent.Semaphore;
public class Main {
	public static void main(String[] args) throws InterruptedException {
		Semaphore semaphore = new Semaphore(2); // 2 permits, like a connection pool of size 2
		semaphore.acquire();
		System.out.println("Permit acquired, available: " + semaphore.availablePermits());
		semaphore.release();
	}
}

Fair Lock Queueing

By default, Java locks make no guarantee about the order in which waiting threads acquire a contested lock. Enabling fairness in a ReentrantLock's constructor forces the lock to grant access to whichever thread has been waiting longest, at some cost to raw throughput.

Example: Fair Lock Queueing

java
import java.util.concurrent.locks.ReentrantLock;
public class Main {
	public static void main(String[] args) {
		ReentrantLock fairLock = new ReentrantLock(true); // grants access in waiting order
		fairLock.lock();
		try { System.out.println("Longest-waiting thread goes first"); } finally { fairLock.unlock(); }
	}
}

Login to run this code

C/C++/Java/PHP execution requires a free account. Your code is saved — you'll land right back in the editor after logging in.