Network Security Internet Technology Development Database Servers Mobile Phone Android Software Apple Software Computer Software News IT Information

In addition to Weibo, there is also WeChat

Please pay attention

WeChat public account

Shulou

What is the use of the new StampedLock in JDK8

2025-02-28 Update From: SLTechnology News&Howtos shulou NAV: SLTechnology News&Howtos > Internet Technology >

Share

Shulou(Shulou.com)06/02 Report--

This article mainly explains "what is the role of the new StampedLock in JDK8". Interested friends may wish to take a look. The method introduced in this paper is simple, fast and practical. Let's let the editor take you to learn "what is the role of the new StampedLock in JDK8?"

A brief introduction to StampedLock

The StampedLock class, introduced in JDK1.8, is an enhancement of the read-write lock ReentrantReadWriteLock, which provides some functions to optimize the access of read-write locks, and at the same time make the read-write locks can be converted to each other and control concurrency more finely.

First of all, it is clear that this class is originally designed as an internal tool class to assist in the development of other thread-safe components. If used well, this class can improve system performance and easily lead to deadlocks and other inexplicable problems.

1.1 introduction of StampedLock

The last article explained read-write locks-- ReentrantReadWriteLock principle in detail, so why introduce StampedLock when you have ReentrantReadWriteLock?

ReentrantReadWriteLock allows multiple readers to hold the read lock at the same time (as long as the write lock is not occupied), while the write lock is exclusive.

However, if read-write locks are not used properly, it is easy to create "hunger" problems:

For example, in the case of a large number of reading threads and few writing threads, it is easy to lead to "hunger" of writing threads, although the use of "fair" strategy can alleviate this problem to some extent, but the "fair" strategy comes at the expense of system throughput.

1.2 Features of StampedLock

The try series acquires the function of the lock and returns a stamp value of 0 when it fails to acquire the lock. The stamp value returned when the lock is acquired needs to be passed in when the methods of releasing and converting the lock are called.

The internal implementation of StampedLockd is based on CLH lock, the principle of CLH lock: the lock maintains a queue of waiting threads, and all threads that apply for the lock and fail are recorded in the queue. A node represents a thread.

A tag bit locked is saved to determine whether the current thread has released the lock. When a thread tries to acquire a lock, it iterates whether all the preorder nodes have successfully released the lock from the tail node of the queue as the preorder node.

II. Examples of using StampedLock

Let's first take a look at an official example of Oracle:

Class Point {private double x, y; private final StampedLock sl = new StampedLock (); void move (double deltaX, double deltaY) {long stamp = sl.writeLock (); / / involving the modification of shared resources, using write lock-exclusive operation try {x + = deltaX; y + = deltaY;} finally {sl.unlockWrite (stamp) }} / * use optimistic read locks to access shared resources * Note: optimistic read locks need to copy a copy of the variables to be operated on the method stack to ensure data consistency, and other writing threads may have modified the data when manipulating the data, * and we are operating on the data in the method stack, that is, a snapshot So what is returned at most is not the latest data, but consistency is guaranteed. * * @ return * / double distanceFromOrigin () {long stamp = sl.tryOptimisticRead (); / / use optimistic read locks double currentX = x, currentY = y / / copy the shared resources to the local method stack if (! sl.validate (stamp)) {/ / if a write lock is occupied, it may cause data inconsistency, so switch to the normal read lock mode stamp = sl.readLock (); try {currentX = x; currentY = y } finally {sl.unlockRead (stamp);}} return Math.sqrt (currentX * currentX + currentY * currentY);} void moveIfAtOrigin (double newX, double newY) {/ / upgrade / / Could instead start with optimistic, not read mode long stamp = sl.readLock () Try {while (x = = 0 & & y = = 0) {long ws = sl.tryConvertToWriteLock (stamp); / / convert a read lock to a write lock if (ws! = 0L) {stamp = ws; x = newX; y = newY; break } else {sl.unlockRead (stamp); stamp = sl.writeLock ();} finally {sl.unlock (stamp);}

As you can see, the most special thing about the above example is the distanceFromOrigin method, which uses the "Optimistic reading" optimistic read lock so that reads and writes can be executed concurrently, but the use of "Optimistic reading" must follow the following pattern:

Long stamp = lock.tryOptimisticRead (); / / get version information copyVaraibale2ThreadMemory () without blocking; / / copy variables to thread local stack if (! lock.validate (stamp)) {/ / verify long stamp = lock.readLock (); / / acquire read lock try {copyVaraibale2ThreadMemory () / / copy the variables to the thread local stack} finally {lock.unlock (stamp); / / release the pessimistic lock}} useThreadMemoryVarables (); / / use the data in the thread local stack for manipulation. 3. StampedLock principle 3.1 StampedLock internal constants

Although StampedLock does not define internal classes to implement AQS framework like other locks, the basic implementation idea of StampedLock is to use CLH queues to manage threads and synchronize state values to represent the state and type of locks.

There are many constants defined inside StampedLock. The fundamental purpose of defining these constants is the same as ReentrantReadWriteLock, which divides the synchronization status value by bit to operate on State through bit operation:

For StampedLock, the flag that the write lock is occupied is bit 8 is 1, the read lock uses 0-7 bits, normally the number of read locks is 1-126, and when it exceeds 126, an int integer named readerOverflow is used to save the excess number.

In addition, StampedLock optimizes multicore CPU compared to ReentrantReadWriteLock. You can see that when the number of CPU cores exceeds 1, there will be some spin operations:

3.2 sample analysis

Suppose you now have multiple threads: ThreadA, ThreadB, ThreadC, ThreadD, ThreadE. Do the following:

ThreadA calls writeLock---- to get the write lock

ThreadB calls readLock---- to get the read lock

ThreadC calls readLock---- to get the read lock

ThreadD calls writeLock---- to get the write lock

ThreadE calls readLock---- to get the read lock

1. Creation of StampedLock object

The constructor of StampedLock is very simple. When constructing, set the synchronization status value:

/ * Creates a new lock, initially in unlocked state. * / public StampedLock () {state = ORIGIN;}

In addition, StamedLock provides three types of views:

/ / viewstransient ReadLockView readLockView;transient WriteLockView writeLockView;transient ReadWriteLockView readWriteLockView

These views are actually an encapsulation of the StamedLock method, making it easy for users who are used to ReentrantReadWriteLock:

For example, ReadLockView is actually equivalent to the read lock returned by ReentrantReadWriteLock.readLock ()

Final class ReadLockView implements Lock {public void lock () {readLock ();} public void lockInterruptibly () throws InterruptedException {readLockInterruptibly ();} public boolean tryLock () {return tryReadLock ()! = 0L;} public boolean tryLock (long time, TimeUnit unit) throws InterruptedException {return tryReadLock (time, unit)! = 0L;} public void unlock () {unstampedUnlockRead () } public Condition newCondition () {throw new UnsupportedOperationException ();}} 2. ThreadA calls writeLock to acquire the write lock

Take a look at the writeLock method:

Public long writeLock () {long s, next; / / bypass acquireWrite in fully unlocked case only return (s=state) & ABITS) = = 0L & & / / (s=state) & ABITS==0L means that both read and write locks are not used U.compareAndSwapLong (this, STATE, s, next = s + WBIT))? / / CAS operation: set position 8 to 1, indicating that the write lock is occupied next: acquireWrite (false, 0L)) / / if the acquisition fails, call acquireWrite to join the waiting queue}

Note: the above code acquires the write lock. If the acquisition fails, it enters the blocking. Note that the method does not respond to the interrupt. A non-zero indicates that the acquisition is successful.

Bit operations are widely used in StampedLock. Here (s = state) & ABITS = = 0L means that neither the read lock nor the write lock is used, where the write lock can be successfully obtained immediately, and then the CAS operation updates the synchronization status value State.

After the operation is completed, the structure of the waiting queue is as follows:

Note: in StampedLock, the nodes in the waiting queue are simpler than those in AQS, with only three states.

0: initial statu

-1: waiting

1: cancel

In addition, there is a cowait field in the definition of the node, which points to a stack that holds the reading thread, which will be discussed later.

3. ThreadB calls readLock to acquire the read lock

Take a look at the readLock method:

Since ThreadA holds a write lock at this time, ThreadB fails to acquire the read lock. The acquireRead method will be called to join the waiting queue:

Public long readLock () {long s = state, next; / / bypass acquireRead on common uncontended case return ((whead = = wtail & & (s & ABITS))

< RFULL &&//表示写锁未被占用,且读锁数量没用超限 U.compareAndSwapLong(this, STATE, s, next = s + RUNIT)) ? next : acquireRead(false, 0L));} 说明:上述代码获取读锁,如果写锁被占用,线程会阻塞,注意该方法不响应中断,返回非0表示获取成功。 acquireRead方法非常复杂,用到了大量自旋操作: /** * 尝试自旋的获取读锁, 获取不到则加入等待队列, 并阻塞线程 * * @param interruptible true 表示检测中断, 如果线程被中断过, 则最终返回INTERRUPTED * @param deadline 如果非0, 则表示限时获取 * @return 非0表示获取成功, INTERRUPTED表示中途被中断过 */private long acquireRead(boolean interruptible, long deadline) { WNode node = null, p; // node指向入队结点, p指向入队前的队尾结点 /** * 自旋入队操作 * 如果写锁未被占用, 则立即尝试获取读锁, 获取成功则返回. * 如果写锁被占用, 则将当前读线程包装成结点, 并插入等待队列(如果队尾是写结点,直接链接到队尾;否则,链接到队尾读结点的栈中) */ for (int spins = -1; ; ) { WNode h; if ((h = whead) == (p = wtail)) { // 如果队列为空或只有头结点, 则会立即尝试获取读锁 for (long m, s, ns; ; ) { if ((m = (s = state) & ABITS) < RFULL ? // 判断写锁是否被占用 U.compareAndSwapLong(this, STATE, s, ns = s + RUNIT) : //写锁未占用,且读锁数量未超限, 则更新同步状态 (m < WBIT && (ns = tryIncReaderOverflow(s)) != 0L)) //写锁未占用,但读锁数量超限, 超出部分放到readerOverflow字段中 return ns; // 获取成功后, 直接返回 else if (m >

= WBIT) {/ / write lock is occupied to detect randomly whether to exit spin if (spins > 0) {if (LockSupport.nextSecondarySeed () > = 0)-- spins } else {if (spins = = 0) {WNode nh = whead, np = wtail; if ((nh = = h & & np = = p) | | (h = nh)! = (p = np)) break } spins = SPINS;} if (p = = null) {/ / p = = null indicates that the queue is empty, then initialize the queue (construct header node) WNode hd = new WNode (WMODE, null) If (U.compareAndSwapObject (this, WHEAD, null, hd)) wtail = hd;} else if (node = = null) {/ / package the current thread as the read node node = new WNode (RMODE, p) } else if (h = = p | | p.mode! = RMODE) {/ / if the queue has only one header node, or if the end node of the queue is not a read node, link the node directly to the end of the queue, and exit spin if (node.prev! = p) node.prev = p after the link is completed. Else if (U.compareAndSwapObject (this, WTAIL, p, node)) {p.next = node; break }} / / if the queue is not empty and the end of the queue is a read node, the current node will be added to the cowait chain of the queue tail node (actually forming a stack, p is the top pointer of the stack) else if (! U.compareAndSwapObject (p, WCOWAIT, node.cowait = p.cowait, node)) {/ / CAS operates the cowait field of the queue tail node p In fact, it is the head insertion method to insert the node node.cowait = null } else {for (;;) {WNode pp, c; Thread w / / try to wake up the first element in the cowait of the header node. The read lock will release the cowait chain if ((h = whead)! = null & & (c = h.cowait)! = null & & U.compareAndSwapObject (h, WCOWAIT, c) through a loop C.cowait) & & (w = c.thread)! = null) / / help release U.unpark (w) If (h = = (pp = p.prev) | | h = = p | | pp = = null) {long m, s, ns; do {if ((m = (s = state) & ABITS)

< RFULL ? U.compareAndSwapLong(this, STATE, s, ns = s + RUNIT) : (m < WBIT && (ns = tryIncReaderOverflow(s)) != 0L)) return ns; } while (m < WBIT); } if (whead == h && p.prev == pp) { long time; if (pp == null || h == p || p.status >

0) {node = null; / / throw away break;} if (deadline = = 0L) time = 0L; else if ((time = deadline-System.nanoTime ()) = 0)-spins } else if ((p = wtail) = = null) {/ / queue is empty, initialize the queue and construct the header node of the queue WNode hd = new WNode (WMODE, null); if (U.compareAndSwapObject (this, WHEAD, null, hd)) wtail = hd } else if (node = = null) / / packages the current thread as a write node node = new WNode (WMODE, p); else if (node.prev! = p) node.prev = p; else if (U.compareAndSwapObject (this, WTAIL, p, node)) {/ / Link node to the end of the queue p.next = node; break }} for (int spins =-1;;) {WNode h, np, pp; int ps; if ((h = whead) = = p) {/ / if the current node is the first node of the queue, immediately attempt to acquire the write lock if (spins < 0) spins = HEAD_SPINS Else if (spins < MAX_HEAD_SPINS) spins

Welcome to subscribe "Shulou Technology Information " to get latest news, interesting things and hot topics in the IT industry, and controls the hottest and latest Internet news, technology news and IT industry trends.

Views: 0

*The comments in the above article only represent the author's personal views and do not represent the views and positions of this website. If you have more insights, please feel free to contribute and share.

Share To

Internet Technology

Wechat

© 2024 shulou.com SLNews company. All rights reserved.

12
Report