The introduction

The CopyOnWrite container has some disadvantages as well:

  • Memory footprint: Because CopyOnWrite’s copy-on-write mechanism has two array objects in memory for each write operation, if the memory footprint of the array object is large, frequent writes will result in frequent Yong and Full GC

  • Data consistency problem: CopyOnWrite container can only guarantee final data consistency, not real-time data consistency. The reading thread may not immediately read the newly modified data because the modification takes place on the replica. But eventually the modification is done and the container is updated so this is the final consistency. Had said that to solve the two shortcomings. We can use the Collections synchronizedList () instead, find a is on the list to add and delete methods are added synchronized implementation. Synchronized is an exclusive lock. If you don’t know what an exclusive lock is, check out the article “Introduction to Java locks”, which basically introduces all the locks in Java. However, there is a performance problem in this case. If the scenario with more reads and less writes requires each read to acquire the lock and then release the lock after reading, then each read request needs to acquire the lock, but the read request does not cause data insecurity, which will cause a performance bottleneck. To solve this problem, a new type of lock, ReadWriteLock, has emerged.

What is a read/write lock

According to the name we can also guess about, is that there are two locks, respectively read lock and write lock. A read lock can be acquired by multiple readers at the same time, but all readers and other writers are blocked when the writer thread accesses it. Only one writer thread can acquire a write lock at a time, and all others will be blocked. Read/write locks actually maintain two locks, a read lock and a write lock. They are distinguished by read locks and write locks. In the case of more read locks and less write locks, the concurrency is greatly improved than that of exclusive locks. The Java implementation for reading and writing locks is ReentrantReadWriteLock, which has the following features:

u

  • Fair selection: support unfair (default) and fair lock acquisition methods, throughput or unfair is better than fair;

  • Reentrant: Support reentrant, read lock can be acquired again, write lock can be acquired again, but also can be acquired read lock;

  • Lock degradation: A write lock can be degraded to a read lock by following the sequence of acquiring a write lock, acquiring a read lock, and releasing the write lock

The use of ReentrantReadWriteLock

We first from the website to a case docs.oracle.com/javase/8/do…

`class RWDictionary {` `private final Map<String, Data> m = new TreeMap<String, Data>(); ` `private final ReentrantReadWriteLock rwl = new ReentrantReadWriteLock(); ` `private final Lock r = rwl.readLock(); ` `private final Lock w = rwl.writeLock(); ` `public Data get(String key) {` `r.lock(); ` `try { return m.get(key); }` `finally { r.unlock(); }` `}` `public String[] allKeys() {` `r.lock(); ` `try { return m.keySet().toArray(); }` `finally { r.unlock(); }` `}` `public Data put(String key, Data value) {` `w.lock(); ` `try { return m.put(key, value); }` `finally { w.unlock(); }` `}` `public void clear() {` `w.lock(); ` `try { m.clear(); }` `finally { w.unlock(); } ` `} ` ` `}Copy the code

ReentrantLock: ReentrantLock: ReentrantLock: ReentrantLock: ReentrantLock: ReentrantLock: ReentrantLock: ReentrantLock: ReentrantLock

Read and write lock implementation analysis

We know that ReentrantLock controls the state of the lock through state, Semaphore, CountDownLatch, CyclicBarrier ReentrantReadWriteLock is implemented using state, but state is an int that reads and writes locks.

Realization analysis of read and write lock state

If we have looked at the thread pool source, we know that the thread pool state and thread count are determined by oneintType atomic variable (high3Bit saves the running state, low29Bit save thread count) to control. The sameReentrantReadWriteLockAlso through astateThe high16And the low16Bits to control the read state and the write state respectively.

Let’s take a look at how it implements read/write separation with a single field,

`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; } `Copy the code
  • SharedCount: indicates the number of read locks. Yes Move the synchronization status (int C) unsigned 16 bits to the right, that is, take the higher 16 bits of the synchronization status.

  • We need to look at the static variable EXCLUSIVE_MASK: It’s 1 and it’s shifted 16 bits to the left and then it’s minus 1 which is 0X0000FFFF which is (1 << SHARED_SHIFT) -1 = 0X0000FFFF so exclusiveCount is going to be equal to C&0x0000FFFF So the lower 16 bits represent the number of times a write lock is acquired.

Source code analysis

ReentrantReadWriteLock ¶ ReentrantReadWriteLock ¶ ReentrantReadWriteLock ¶ ReentrantReadWriteLock ¶ ReentrantReadWriteLock ¶ ReentrantReadWriteLock ¶

`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) ` ` / / the current state is not 0, but write locks of 0 means read lock is not 0 ` ` / / when a read lock has been thread or the current thread is not have access to write thread lock failed to get write lock ` ` if (w = = 0 | | current! = getExclusiveOwnerThread())` `return false; ` `if (w + exclusiveCount(acquires) > MAX_COUNT)` `throw new Error("Maximum lock count exceeded"); // Acquire setState(c + acquires); ` `return true; ` `} ` ` / / writerShouldBlock fair locked and not fair judgment ` ` if (writerShouldBlock () | | ` `! compareAndSetState(c, c + acquires))` `return false; ` `setExclusiveOwnerThread(current); ` `return true; ` ` `}Copy the code

TryAcquireShared is a shared lock, so you should rewrite tryAcquireShared as well. In fact, it’s relatively easy to look at these things based on AQS once you understand them.

Upgrade and degrade read/write locks

We mentioned earlier that read and write locks can be degraded, but did not say whether they can be upgraded. Let’s first look at what are lock downgrades and lock upgrades

  • Lock degradation: from write lock to read lock; It holds the write lock, acquires the read lock, and releases the write lock. If you hold a write lock, release the write lock, and then acquire the read lock, this is not lock degradation.

  • Why lock downgrade?

u

In order to ensure the visibility of data, if the current thread does not acquire the read lock but directly releases the write lock, assuming that another thread (called thread T) acquires the write lock and changes the data, the current thread will not be aware of the data update of thread T. If the current thread acquires a read lock, that is, following the steps of lock degradation, thread T will be blocked until the current thread uses the data and releases the read lock. Thread T can acquire the write lock to update the data. From The Art of Concurrent Programming in Java

  • Lock upgrade: Change from read lock to write lock. Hold the read lock first and then acquire the write lock (this will not succeed). Since acquiring the write lock is an exclusive lock, if any read locks are occupied, the write lock will be queued until all the read locks are released.

To consider

  • This article mainly introduces the single read and write lock, if you want to achieve a distributed read and write lock how to achieve?

  • How is hunger in ReentrantReadWriteLock addressed? ReentrantReadWriteLock implements read/write separation. To obtain a read lock, you must ensure that there are no other read/write locks. However, it is difficult to obtain a write lock when there are many read operations because the read lock may exist all the time. Can’t get a write lock.

The end of the

  • As a result of their talent and learning, it is inevitable that there will be mistakes, if you found the wrong place, but also hope that the message to me pointed out, I will correct it.

  • If you think the article is good, your forwarding, sharing, appreciation, like, message is the biggest encouragement to me.

  • Thank you for reading. Welcome and thank you for your attention.

  • Picking apples on the shoulders of giants:

    The Art of Concurrent Programming

    javajr.cn

Phase to select

Recommend 👍 :CyclicBarrier of Java high concurrency programming foundation

Recommended 👍 : CountDownLatch for Java high concurrency programming foundation

Recommended 👍 : Semaphore for Java high concurrency programming foundation

Recommended 👍 : AQS for Java high Concurrency programming basics

Recommended 👍 : Abominable crawler directly put the production of 6 machines to climb hanging!

Copy the code