mutlugazete.com

# Understanding Java Lock Synchronization Optimization

Written on

Chapter 1: Introduction to Java Lock Synchronization

In a previous discussion, I examined various optimization techniques for the synchronized lock at the JVM level. Furthermore, since JDK 1.5, Java introduced the Lock synchronization mechanism. What benefits does this offer?

The synchronized lock automatically manages lock acquisition and release via the JVM, whereas the Lock mechanism requires developers to manually acquire and release locks, granting greater operational flexibility. While the Lock primarily utilizes optimistic locking for its fundamental operations, it can still operate under pessimistic locking when threads are blocked. To comprehend the differences between these two types of locks, let’s look at a comparative diagram:

Comparison of synchronized and Lock mechanisms

When it comes to performance, under conditions of low concurrency and minimal contention, both the synchronized lock and the Lock perform comparably due to the hierarchical locking advantages of the synchronized lock. However, in high-load and high-concurrency environments, the synchronized lock tends to escalate to a heavier locking mechanism under severe contention, leading to less stable performance compared to the Lock.

We can visualize the performance differences through a straightforward series of tests, illustrated below:

Performance comparison of synchronized and Lock

From these comparisons, it's evident that the Lock demonstrates more consistent performance. Now, let’s explore how the implementation of Lock differs from the synchronized locks discussed earlier.

Chapter 2: Understanding Lock Implementation

The Lock is a Java interface, with common implementations such as ReentrantLock and ReentrantReadWriteLock (RRW), both of which are based on the AbstractQueuedSynchronizer (AQS) class.

The AQS structure includes a wait queue (known as the CLH queue), which is built on a linked list to hold all blocked threads. It also contains a state variable that indicates the locking state for ReentrantLock. The operations on this queue utilize CAS (Compare-And-Swap) operations. Below is a diagram illustrating the entire lock acquisition process:

Lock acquisition process diagram

Section 2.1: Lock Separation Optimization

Despite the stable performance of the Lock, not every scenario necessitates the exclusive use of ReentrantLock for achieving thread synchronization.

Consider a situation where one thread reads data while another writes to the same data. This can lead to inconsistencies in the data read. Similarly, if two threads write to the same data concurrently, the values observed by each thread may differ. In such cases, employing a mutex lock during read and write operations ensures that only one thread can modify or read the data at any time.

Typically, in many business applications, read operations outnumber write operations. Since read operations do not alter the shared resource’s data, locking the resource can degrade concurrency performance. Hence, we may ask: is there a way to enhance the locking mechanism in such cases?

#### Subsection 2.1.1: ReentrantReadWriteLock

For scenarios characterized by more frequent read operations than writes, Java offers the ReadWriteLock implementation, specifically the ReentrantReadWriteLock (RRW). Unlike ReentrantLock, which permits only one thread access at a time, RRW allows multiple threads to read concurrently, while preventing any writing during those reads.

Internally, the read-write lock maintains two distinct locks: one for read operations (ReadLock) and another for write operations (WriteLock).

How does the read-write lock manage lock separation to ensure the integrity of shared resources? RRW is also built on AQS, with its custom synchronizer (inheriting AQS) designed to track the status of multiple reading threads and a single writing thread. This is achieved through an ingenious use of bits to manage two states within an integer. The high 16 bits represent read operations, while the low 16 bits denote write operations.

When a thread seeks to acquire a write lock, it first checks if the synchronization state (state) is 0, indicating that no other thread currently holds the lock. If the state is non-zero, it further checks the low 16 bits (w) of the synchronization state. If w is 0, other threads have the read lock, prompting the thread to enter the CLH queue and wait. Conversely, if w is non-zero, the thread must verify if it is the one attempting to acquire the write lock. If not, it enters the queue; if yes, it checks if it has exceeded the maximum write lock acquisitions. If it has, an exception occurs; otherwise, the synchronization state is updated.

The process for acquiring a read lock is similar. A thread first checks if the synchronization state is 0. If so, it indicates that no other thread is holding the lock. If blocking is required, it enters the CLH queue. If not, it proceeds to update the synchronization state to indicate a read lock using CAS.

Here is an example showcasing the implementation of ReentrantReadWriteLock (RRW) through a square calculation:

public class TestRTTLock {

private double x, y;

private ReentrantReadWriteLock lock = new ReentrantReadWriteLock();

private Lock readLock = lock.readLock();

private Lock writeLock = lock.writeLock();

public double read() {

// obtaining a read lock

readLock.lock();

try {

return Math.sqrt(x * x + y * y);

} finally {

// release read lock

readLock.unlock();

}

}

public void move(double deltaX, double deltaY) {

// obtaining a write lock

writeLock.lock();

try {

x += deltaX;

y += deltaY;

} finally {

// release write lock

writeLock.unlock();

}

}

}

#### Subsection 2.1.2: StampedLock

While ReentrantReadWriteLock (RRW) is effective when reads surpass writes, it may still lead to performance issues. Specifically, write threads might experience starvation if they cannot acquire the lock for extended periods.

To address this, JDK 1.8 introduced the StampedLock class. Unlike RRW, StampedLock operates on a similar principle to AQS but features three modes of lock control: write, pessimistic read, and optimistic read. Additionally, acquiring a lock with StampedLock returns a “stamp” that must be validated upon release. When in optimistic read mode, this stamp is further validated after the read operation.

Let's examine an official example demonstrating the usage of StampedLock:

public class Point {

private double x, y;

private final StampedLock s1 = new StampedLock();

void move(double deltaX, double deltaY) {

// obtaining a write lock

long stamp = s1.writeLock();

try {

x += deltaX;

y += deltaY;

} finally {

// release write lock

s1.unlockWrite(stamp);

}

}

double distanceFromOrigin() {

// optimistic read operation

long stamp = s1.tryOptimisticRead();

// copy variables

double currentX = x, currentY = y;

// check if there is a write operation during reading

if (!s1.validate(stamp)) {

// upgrade to pessimistic reading

stamp = s1.readLock();

try {

currentX = x;

currentY = y;

} finally {

s1.unlockRead(stamp);

}

}

return Math.sqrt(currentX * currentX + currentY * currentY);

}

}

During the process of a write thread acquiring a write lock, it initially secures a stamp through the writeLock method. Since writeLock is exclusive, only one thread can hold this lock at any time. Other threads must wait until no thread possesses a read or write lock. Upon successful acquisition, a stamp indicating the lock version is returned. Releasing the lock requires calling unlockWrite with the stamp parameter.

In the case of a read thread acquiring the lock, it first attempts to obtain a stamp using optimistic locking via tryOptimisticRead. If no write lock is held, a non-zero stamp is returned, and the thread copies the shared resource onto the stack. The thread then validates the stamp to check for any intervening write locks. If a write lock exists, it must upgrade to a pessimistic lock.

Compared to RRW, StampedLock employs bitwise OR operations to check for read locks, which eliminates the need for CAS operations. This efficient process allows for immediate upgrades to pessimistic locks even after a failed optimistic lock attempt, reducing CPU overhead.

Conclusion

Whether you opt for a synchronized lock or a Lock, thread blocking is a common issue when locks are contended, leading to frequent context switching and increased performance costs. Thus, minimizing lock contention is essential for optimizing locks.

As we’ve seen with synchronized locks, decreasing lock granularity and reducing the duration of lock occupancy can mitigate contention. This discussion highlighted how the flexibility of Lock allows for lock separation, enhancing performance in scenarios where read operations significantly outnumber writes. From the foundational ReentrantReadWriteLock (RRW) to the separation of read and write locks, as well as the innovative approach of StampedLock with its optimistic, pessimistic, and write locks, all these strategies are aimed at reducing contention and boosting concurrency in the system.

Share the page:

Twitter Facebook Reddit LinkIn

-----------------------

Recent Post:

# 22 Proven Strategies for Enhancing Your Sleep Quality

Discover 22 effective strategies to improve your sleep quality and duration for a healthier lifestyle.

Engaging for Success: Enhance Your Focus in Software Development

Discover how improving engagement in key areas can elevate your software development career.

Insights for Pisces Entrepreneurs: A Weekly Forecast

Explore predictions for Pisces entrepreneurs focusing on health, family, and career insights for the week of August 27, 2023.

COP26: Tuvalu's Heartbreaking Message from a Sinking Nation

Tuvalu's poignant address at COP26 highlights the urgent threats of climate change as the nation faces the reality of rising sea levels.

The Transformative Power of Yoga on Brain Chemistry

Explore how yoga influences brain chemistry and enhances well-being, shedding light on the science behind this ancient practice.

Write Freely: Unleash Your Creativity Without Fear of Judgment

Discover how to write without fear of judgment and embrace your unique voice in the world of creativity.

Transform Your Life with These 3 Must-Read Books for Everyone

Explore three transformative books that can enrich your life and perspective, along with insightful video recommendations.

# Toyota's Ammonia Engine: A Game-Changer for Sustainable Transport

Discover how Toyota's ammonia-powered engine could transform the future of eco-friendly transportation.