Java multithreading 7: reentrantreadwritelock

Let me stay 2022-01-26 16:53:52 阅读数:686

java multithreading reentrantreadwritelock

Real multithreaded business development , The most commonly used logic is data reading and writing ,ReentrantLock Although it has a completely exclusive effect ( That is, only one thread is executing at the same time lock The next task ),

This ensures thread safety of instance variables , But it's very inefficient . So in JDK A read-write lock is provided in ReentrantReadWriteLock class , Use it to speed up operation efficiency .

Read write locks represent two locks , One is read operation related lock , be called Shared lock ; The other is the lock related to write operation , be called Exclusive lock .

Let's verify the mutex between read-write locks through code

ReentrantReadWriteLock

Read read share

First create an object , Define a read lock method and a write lock method respectively ,

public class MyDomain3 {
private ReentrantReadWriteLock lock = new ReentrantReadWriteLock();
public void testReadLock() {
try {
lock.readLock().lock();
System.out.println(System.currentTimeMillis() + " Get read lock ");
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
lock.readLock().unlock();
}
}
public void testWriteLock() {
try {
lock.writeLock().lock();
System.out.println(System.currentTimeMillis() + " Get write lock ");
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
lock.writeLock().unlock();
}
}
}

Create thread class 1 Call the read lock method

public class Mythread3_1 extends Thread {
private MyDomain3 myDomain3;
public Mythread3_1(MyDomain3 myDomain3) {
this.myDomain3 = myDomain3;
}
@Override
public void run() {
myDomain3.testReadLock();
}
}

@Test
public void test3() throws InterruptedException {
MyDomain3 myDomain3 = new MyDomain3();
Mythread3_1 readLock = new Mythread3_1(myDomain3);
Mythread3_1 readLock2 = new Mythread3_1(myDomain3);
readLock.start();
readLock2.start();
Thread.sleep(3000);
}

Execution results :

1639621812838 Get read lock
1639621812839 Get read lock

It can be seen that the two read locks are executed almost simultaneously , Explain that reading and reading are shared , Because the read operation will not have thread safety problems .

 

Writing mutually exclusive

Create thread class 2, Call the write lock method

public class Mythread3_2 extends Thread {
private MyDomain3 myDomain3;
public Mythread3_2(MyDomain3 myDomain3) {
this.myDomain3 = myDomain3;
}
@Override
public void run() {
myDomain3.testWriteLock();
}
}
@Test
public void test3() throws InterruptedException {
MyDomain3 myDomain3 = new MyDomain3();
Mythread3_2 writeLock = new Mythread3_2(myDomain3);
Mythread3_2 writeLock2 = new Mythread3_2(myDomain3);
writeLock.start();
writeLock2.start();
Thread.sleep(3000);
}

Execution results :

1639622063226 Get write lock
1639622064226 Get write lock

In terms of time , The interval is 1000ms namely 1s, Write lock and write lock are mutually exclusive .

 

Reading and writing are mutually exclusive

Reuse thread 1 And thread 2 Call read lock and write lock respectively

@Test
public void test3() throws InterruptedException {
MyDomain3 myDomain3 = new MyDomain3();
Mythread3_1 readLock = new Mythread3_1(myDomain3);
Mythread3_2 writeLock = new Mythread3_2(myDomain3);
readLock.start();
writeLock.start();
Thread.sleep(3000);
}

Execution results :

1639622338402 Get read lock
1639622339402 Get write lock

In terms of time , The interval is 1000ms namely 1s, It is consistent with the code , It is proved that reading and writing are mutually exclusive .

Pay attention to the ," Reading and writing are mutually exclusive " and " Write and read are mutually exclusive " It's two different scenarios , But the way of proof is consistent with the conclusion , So it doesn't prove .

 

Under the final test results :

1、 Read and read are not mutually exclusive , Because the read operation will not have thread safety problems

2、 Write and write are mutually exclusive , Avoid one write operation affecting another , Raises thread safety issues

3、 Read and write are mutually exclusive , Avoid that the write operation modifies the content during the read operation , Raises thread safety issues

In summary , Multiple Thread Can read at the same time , But only one is allowed at the same time Thread Write operation .

 

Source code analysis

In the read-write lock Sync It is also realized AQS, To recall ReentrantLock Implementation of custom synchronizer in , The synchronization state indicates the number of times the lock is repeatedly acquired by a thread ,

The custom synchronizer of the read-write lock needs to be in the synchronization state ( An integer variable ) Maintain the status of multiple read threads and one write thread , The design of this state is the key to the implementation of read-write lock .

The read-write lock divides the variable into two parts , high 16 Bit means read , low 16 Bit means write

 

The current synchronization state indicates that a thread has acquired a write lock , And re entered twice , At the same time, it also obtains two consecutive read locks . How does the read-write lock quickly determine the state of each reading and writing ?

static final int SHARED_SHIFT = 16;
static final int SHARED_UNIT = (1 << SHARED_SHIFT);
static final int MAX_COUNT = (1 << SHARED_SHIFT) - 1;
static final int EXCLUSIVE_MASK = (1 << SHARED_SHIFT) - 1;
/** Returns the number of shared holds represented in count */
static int sharedCount(int c) { return c >>> SHARED_SHIFT; }
/** Returns the number of exclusive holds represented in count */
static int exclusiveCount(int c) { return c & EXCLUSIVE_MASK; }

In fact, it is through bit operation . Assume the current synchronization state value is c, Write state is equal to c & EXCLUSIVE_MASK (c&0x0000FFFF( Will be high 16 Erase all the bits )),

The state of reading is equal to c>>>16( Make up without symbols 0 Move right 16 position ). When the write state increases 1 when , be equal to c+1, When the reading state increases 1 when , be equal to c+(1<<16), That is to say c+0x00010000.

A corollary can be drawn from the division of states :c It's not equal to 0 when , When writing state (c & 0x0000FFFF) be equal to 0 when , Then read the state (c>>>16) Greater than 0, The read lock has been acquired .

 

Acquisition and release of write lock

Pass the test above , We know that a write lock is an exclusive lock that supports reentry , Look at how the source code realizes the acquisition of write lock

protected final boolean tryAcquire(int acquires) {
/*
* Walkthrough:
* 1. If read count nonzero or write count nonzero
* and owner is a different thread, fail.
* 2. If count would saturate, fail. (This can only
* happen if count is already nonzero.)
* 3. Otherwise, this thread is eligible for lock if
* it is either a reentrant acquire or
* queue policy allows it. If so, update state
* and set owner.
*/
Thread current = Thread.currentThread();
int c = getState();
int w = exclusiveCount(c);
if (c != 0) {
// (Note: if c != 0 and w == 0 then shared count != 0)
if (w == 0 || current != getExclusiveOwnerThread())
return false;
if (w + exclusiveCount(acquires) > MAX_COUNT)
throw new Error("Maximum lock count exceeded");
// Reentrant acquire
setState(c + acquires);
return true;
}
if (writerShouldBlock() ||
!compareAndSetState(c, c + acquires))
return false;
setExclusiveOwnerThread(current);
return true;
}

The first 3 Go to the first place 11 That's ok , Briefly describe the implementation logic of the whole method , Here's a compliment , This comment makes it easy for people to know the function of the code . Let's analyze ,

The first 13 To the first 15 That's ok , Get the current thread object respectively current,lock Lock state value of c And the value of the write lock w,c!=0 indicate Currently in a locked state ,

Continue to analyze the second 16 Row to 25 That's ok , There's a key Note:(Note: if c != 0 and w == 0 then shared count != 0): In short, it is : If a has a lock state but no write lock , Then it must have a read lock .

The first 18 That's ok if Conditions , Is to judge that the read lock is added , But the current thread is not the thread owned by the lock , Then failed to acquire the lock , Prove that read-write locks are mutually exclusive .

The first 20 Go to the first place 25 That's ok , Take this step , explain w !=0 , The write lock has been acquired , As long as the maximum write lock is not exceeded , Then the write lock can be successfully obtained by adding the write state .

If the code goes to the 26 That's ok , explain c==0, There are currently no locks , Execute first writerShouldBlock() Method , This method is used to determine whether the write lock should be blocked ,

This is a lock on fairness and unfairness. There will be different logic , For unfair locks , Go straight back to false, There is no need to block ,

The following is the judgment of fair lock execution

public final boolean hasQueuedPredecessors() {
// The correctness of this depends on head being initialized
// before tail and on head.next being accurate if the current
// thread is first in queue.
Node t = tail; // Read fields in reverse initialization order
Node h = head;
Node s;
return h != t &&
((s = h.next) == null || s.thread != Thread.currentThread());
}

For the fair lock, you need to judge whether there is..., in the current waiting queue The thread equal to the current thread and queued to acquire the lock .

The release of write lock and ReentrantLock The release process is basically similar to , Each release reduces the write state , When the write status is 0 Indicates that the write lock has been released ,

So the waiting read-write thread can continue to access the read-write lock , At the same time, the modification of the previous write thread is visible to the subsequent read-write thread .

 

Acquisition and release of read lock

Read lock is a shared lock that supports reentry , It can be acquired by multiple threads at the same time .JDK Source code is as follows :

protected final int tryAcquireShared(int unused) {
Thread current = Thread.currentThread();
int c = getState();
if (exclusiveCount(c) != 0 &&
getExclusiveOwnerThread() != current)
return -1;
int r = sharedCount(c);
if (!readerShouldBlock() &&
r < MAX_COUNT &&
compareAndSetState(c, c + SHARED_UNIT)) {
if (r == 0) {
firstReader = current;
firstReaderHoldCount = 1;
} else if (firstReader == current) {
firstReaderHoldCount++;
} else {
HoldCounter rh = cachedHoldCounter;
if (rh == null || rh.tid != getThreadId(current))
cachedHoldCounter = rh = readHolds.get();
else if (rh.count == 0)
readHolds.set(rh);
rh.count++;
}
return 1;
}
return fullTryAcquireShared(current);
}

The first 4 Go to the first place 6 That's ok , If the write lock is held by another thread , Then return directly false, Failed to get read lock , Prove that writing and reading are mutually exclusive between different threads .

  The first 8 That's ok ,readerShouldBlock() Whether the acquisition of read lock should be blocked , We should also distinguish between fair locks and unfair locks ,

In the fair lock mode, it is necessary to judge whether there is... In the current waiting queue The thread equal to the current thread and queued to acquire the lock , If it exists, you need to wait to obtain the read lock .

In the unfair lock mode, it is necessary to judge that the first one in the current waiting queue is waiting for write lock , Then the method returns true, Obtaining a read lock requires waiting .

fullTryAcquireShared() It mainly deals with the complete version of read lock acquisition , It deals with tryAcquireShared() Not handled in CAS Error and reentrant read lock processing logic .

 

reference

1:《Java The art of concurrent programming 》

2:《Java Multithread programming core technology 》

copyright:author[Let me stay],Please bring the original link to reprint, thank you. https://en.javamana.com/2022/01/202201261653483914.html