Concurrent Programming: The Explicit Lock Principle

Concurrent Programming: The Explicit Lock Principle

In this article, we will delve into the world of concurrent programming and explore the explicit lock principle, a fundamental concept in Java’s concurrency API. We will examine the basic principles of locks, the synchronized keyword, and the ReentrantLock class, which is a key implementation of the explicit lock principle.

Locks: A Basic Understanding

In concurrent programming, locks are used to synchronize access to shared resources, preventing multiple threads from accessing the same resource simultaneously. The JVM provides a built-in lock mechanism, which is simple to use but lacks advanced customization features. For example, it does not support fair competition, regular competition lock timeout, or blocked thread interrupt requests.

The Explicit Lock Principle

To address these limitations, the JDK introduced the explicit lock principle, which allows developers to manage locks at the program level. This approach provides increased flexibility and customization options, but requires a deeper understanding of locks and their behavior.

The Lock Interface

The Lock interface, located in the java.util.concurrent.locks package, defines the basic methods for managing locks:

public interface Lock {
    void lock();
    void lockInterruptibly() throws InterruptedException;
    boolean tryLock();
    boolean tryLock(long time, TimeUnit unit);
    void unlock();
    Condition newCondition();
}

The Lock interface provides a basic framework for managing locks, and its implementations, such as ReentrantLock, provide more complex features.

ReentrantLock: A Key Implementation

ReentrantLock is a key implementation of the explicit lock principle, providing features such as fair competition, regular competition lock timeout, and blocked thread interrupt requests. It is also the most frequently used lock implementation class.

ReentrantLock Constructors

ReentrantLock provides two constructors to support fair competition:

public ReentrantLock() // default constructor, unfair lock
public ReentrantLock(boolean fair) // constructor to enable fair lock

Fair vs. Unfair Locks

Fair locks use the principle of first-come, first-served, while unfair locks disregard this principle. Fair locks ensure that each thread has an equal chance to acquire the lock, but may consume more resources and lead to lower throughput. Unfair locks prioritize throughput over fairness, but may lead to starvation.

ReentrantLock Inner Classes

ReentrantLock provides three inner classes: Sync, NonfairSync, and FairSync. These classes inherit from the AQS (AbstractQueuedSynchronizer) and implement the lock method, which is a specific implementation of the lock mechanism.

AQS: The Core Lock Mechanism

AQS is a core lock mechanism that provides a synchronization mechanism, recording the current thread’s information and the possession of the lock. It is an abstract class that provides a parent class, AbstractOwnableSynchronizer, which has a Thread type member property to save the current AQS possession of the lock thread.

AQS Methods

AQS provides several methods, including tryAcquire, tryRelease, and acquireQueued, which are used to manage the lock and its associated queue.

tryAcquire Method

The tryAcquire method is used to attempt to acquire the lock. If the lock is available, it returns true; otherwise, it returns false.

protected final boolean tryAcquire(int acquires) {
    return nonfairTryAcquire(acquires);
}

tryRelease Method

The tryRelease method is used to attempt to release the lock. If the lock is released successfully, it returns true; otherwise, it returns false.

protected final boolean tryRelease(int releases) {
    int c = getState() - releases;
    if (Thread.currentThread() != getExclusiveOwnerThread()) {
        throw new IllegalMonitorStateException();
    }
    boolean free = false;
    if (c == 0) {
        free = true;
        setExclusiveOwnerThread(null);
    }
    setState(c);
    return free;
}

acquireQueued Method

The acquireQueued method is used to acquire the lock from the queue. If the lock is acquired successfully, it returns true; otherwise, it returns false.

public final boolean acquireQueued(Node node, int arg) {
    // ...
}

parkAndCheckInterrupt Method

The parkAndCheckInterrupt method is used to park the current thread and check for interrupt requests. If the thread is interrupted, it returns true; otherwise, it returns false.

private final boolean parkAndCheckInterrupt() {
    LockSupport.park(this);
    return Thread.interrupted();
}

unlock Method

The unlock method is used to release the lock. If the lock is released successfully, it returns true; otherwise, it returns false.

public void unlock() {
    sync.release(1);
}

In this article, we have explored the explicit lock principle, the Lock interface, and the ReentrantLock class. We have also examined the AQS (AbstractQueuedSynchronizer) and its methods, including tryAcquire, tryRelease, and acquireQueued. Finally, we have discussed the parkAndCheckInterrupt method and the unlock method.