Read write lock of JUC

Read write lock

The read-write lock can allow multiple read threads to access at the same time, but when the write thread accesses, all read threads and other write threads are blocked. The read-write lock maintains a pair of locks, a read lock and a write lock. By separating the read lock and the write lock, the concurrency is greatly improved compared with the general exclusive lock.

Multiple threads can be used for read operations, and only one thread can be used for write operations

Java does not provide read-write locks. The implementation is ReentrantReadWriteLock

characteristic:

  1. Support fair and unfair lock acquisition methods
  2. Re entry is supported: take the read-write thread as an example. After the read thread obtains the read lock, it can obtain the read lock again, while the write thread can also obtain the write lock and the read lock when it has not been fully released when it obtains the write lock.
  3. Lock degradation. Write locks can be downgraded to read locks, but read locks cannot be upgraded to write locks

Definition of lock degradation:

Lock demotion means that a write lock is demoted to a read lock. If the current thread has a write lock, then releases it, and finally obtains a read lock, this segmented process cannot be called lock degradation. Lock degradation refers to the process of holding the (currently owned) write lock, obtaining the read lock, and then releasing the (previously owned) write lock.

Write locks can be downgraded to read lock order: acquire write locks ---- acquire read locks ---- release write locks ---- release read locks.

Its disadvantages: it will cause lock starvation, keep reading and no write operation.

Three states of resources and locks:

  1. No lock, multithreading, resource scrambling
  2. Added locks (Synchronized and ReentrantLock) are exclusive, and read, read and write are exclusive. Only one operation can be performed at a time
  3. The read-write lock can be shared to improve performance, and can be read by multiple people at the same time

The purpose of ReentrantReadWriteLock is to improve the throughput of read operations (it can be used when there are more reads and less writes)

Understanding of readwrite lock reentrant:

The re-entry of read lock allows multiple threads applying for read operation, while the write lock can only be occupied by a single thread at the same time, and the write operation of this thread can be re-entered.

If a thread occupies a write lock, it can also occupy a read lock without releasing the write lock, that is, the degradation of the lock.

If a thread holds a read lock and a write lock at the same time, and the write lock is completely released, it will be converted to a read lock. Later, the write operation cannot be re entered. If the write lock is not fully released, the write operation can be re entered.

Failure example:

package com.RWLock;

import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.TimeUnit;

class MyCache{
    private volatile Map<String,Object> map = new HashMap<String,Object>();

    //Write operation
    public void put(String key,Object value) throws InterruptedException {
        System.out.println(Thread.currentThread().getName()+"\t------Write data"+key);
        TimeUnit.SECONDS.sleep(1);
        map.put(key,value);
        System.out.println(Thread.currentThread().getName()+"\t------Write complete"+key);
    }
    //Read operation
    public void get(String key) throws InterruptedException {
        System.out.println(Thread.currentThread().getName()+"\t------Read data"+key);
        TimeUnit.SECONDS.sleep(1);
        map.get(key);
        System.out.println(Thread.currentThread().getName()+"\t------Read complete"+key);
    }
}


public class readWriteLockDemo {
    public static void main(String[] args) {
        MyCache myCache = new MyCache();
        //Multiple threads write
        for (int i = 1; i <= 5; i++) {
            int finalI = i;
            new Thread(()->{
                try {
                    myCache.put(finalI +"", finalI +"");
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            },String.valueOf(i)).start();
        }
        for (int i = 1; i <= 5; i++) {
            int finalI = i;
            new Thread(()->{
                try {
                    myCache.get(finalI+"");
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            },String.valueOf(i)).start();
        }
    }
}

After using the read-write lock:

Cache combines a non thread safe HashMap as the implementation of cache, and uses the read lock and write lock of read-write lock to ensure that the cache is thread safe. In the read operation get(String key) method, you need to obtain the read lock, so that concurrent access to the method will not be blocked. The write operation put(String key,Object value) method and clear() method must obtain the write lock in advance when updating the HashMap. After obtaining the write lock, the acquisition of read lock and write lock by other threads is blocked, and other read-write operations can continue only after the write lock is released.

package com.RWLock;

import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.ReadWriteLock;
import java.util.concurrent.locks.ReentrantReadWriteLock;

class MyCache{
    private volatile Map<String,Object> map = new HashMap<String,Object>();
    //Reentrant read / write lock
    private ReadWriteLock readWriteLock = new ReentrantReadWriteLock();

    //Write operation
    public void put(String key,Object value) throws InterruptedException {
        try{
            readWriteLock.writeLock().lock(); //Write lock
            System.out.println(Thread.currentThread().getName()+"\t------Write data"+key);
            TimeUnit.SECONDS.sleep(1);
            map.put(key,value);
            System.out.println(Thread.currentThread().getName()+"\t------Write complete"+key);
        }catch (InterruptedException e){
            e.printStackTrace();
        }finally{
            readWriteLock.writeLock().unlock();
        }

    }
    //Read operation
    public void get(String key) {
        try {
            readWriteLock.readLock().lock();   //Read lock
            System.out.println(Thread.currentThread().getName()+"\t------Read data"+key);
            TimeUnit.SECONDS.sleep(1);
            map.get(key);
            System.out.println(Thread.currentThread().getName()+"\t------Read complete"+key);
        }catch (InterruptedException e){
            e.printStackTrace();
        }finally{
            readWriteLock.readLock().unlock();
        }


    }
}


public class readWriteLockDemo {
    public static void main(String[] args) {
        MyCache myCache = new MyCache();
        //Multiple threads write
        for (int i = 1; i <= 5; i++) {
            int finalI = i;
            new Thread(()->{
                try {
                    myCache.put(finalI +"", finalI +"");
                } catch (Exception e) {
                    e.printStackTrace();
                }
            },String.valueOf(i)).start();
        }
        for (int i = 1; i <= 5; i++) {
            int finalI = i;
            new Thread(()->{
                try {
                    myCache.get(finalI+"");
                } catch (Exception e) {
                    e.printStackTrace();
                }
            },String.valueOf(i)).start();
        }
    }
}

Design of read-write lock: it depends on the synchronization state of synchronizer.

The synchronization state indicates the number of times the lock is repeatedly acquired by a thread, and the user-defined synchronizer of the read-write lock needs to maintain the state of multiple read threads and one write thread in the synchronization state (an integer variable), so that the design of this state becomes the key to the implementation of the read-write lock

If an integer variable is maintained, it is cut by bit. The upper 16 bits are read and the lower 16 bits are write.

How do read-write locks quickly determine the respective states of read and write? The answer is through bit operation.

Acquisition and release of write lock:

Write lock is an exclusive lock that supports re-entry;

  1. If the current thread obtains a write lock, the write state is increased and exclusive
  2. If when the current thread (A) acquires the lock again, the read lock has been acquired or the thread is not the thread that has acquired the write lock (personal understanding: if a thread acquires the write lock, the subsequent access of other read-write threads is blocked), the current thread (A) enters the waiting state.

A write lock cannot be obtained after a read lock is obtained, but a read lock can be obtained after a write lock is obtained

Acquisition and release of read lock

Read lock is a shared lock that supports re-entry. It can be acquired by multiple threads at the same time. When no other write thread accesses it (or the write state is 0), the read lock will always be acquired successfully, and all you do is (thread safe) increase the read state.

If other threads have acquired the write lock, the current thread fails to acquire the read lock and enters the waiting state. If the current thread obtains the write lock or the write lock is not obtained, the current thread (thread safety, guaranteed by CAS) increases the read state and successfully obtains the read lock

Added by ganesh on Wed, 12 Jan 2022 10:30:16 +0200