Use of ReadWriteLock in Concurrent Programming Series

Use of ReadWriteLock in Concurrent Programming Series

1. What is ReadWriteLock?

ReadWriteLock is a read-write lock api provided in the juc package of jdk. It maintains a pair of associated read locks and write locks. The read locks can be shared by multiple read threads, and the write locks are exclusive.

2. Why do I need ReadWriteLock?

Previously, we liked to use reentrant lock and reentry lock. Since the api reentrant lock is provided, jdk officially launched ReadWriteLock. Compared with reentrant lock, ReadWriteLock saves resources. Although reentrant lock can also ensure thread safety, it consumes resources. For example, when all threads are read locks, this is thread safety, There is no need for thread safety control. It is OK to directly ensure the parallel execution of threads, but ReentrantLock cannot do so. Therefore, ReadWriteLock ensures thread safety and execution efficiency according to a series of rules.

3. Common API s for ReadWriteLock

ReadWriteLock has two implementation classes in the juc package of jdk8:

Main methods of ReentrantReadWriteLock:

4. Acquisition rules of read-write locks

  1. If a thread has occupied a read lock and other threads want to apply for a read lock, they can apply successfully
  2. If a thread has occupied a read lock and other threads want to apply for a write lock, they cannot apply successfully
  3. If a thread has occupied a write lock, and other threads want to apply for a read lock or a write lock, they cannot apply successfully

To sum up, reading is shared and others are mutually exclusive (write mutually exclusive, read-write mutually exclusive, write read mutually exclusive)

5. Applicable scenarios for ReadWriteLock

After knowing the special effects of ReadWriteLock, we know that compared with ReentrantLock for general occasions, ReadWriteLock is suitable for more reading and less writing. Rational use can further improve the concurrency efficiency

6. ReadWriteLock example

For example, create read and write locks using ReentrantReadWriteLock

import java.util.concurrent.locks.ReentrantReadWriteLock;

public class ReadWriteLockExample {

    private static ReentrantReadWriteLock reentrantReadWriteLock = new ReentrantReadWriteLock();
    private static ReentrantReadWriteLock.ReadLock readLock = reentrantReadWriteLock.readLock();
    private static ReentrantReadWriteLock.WriteLock writeLock = reentrantReadWriteLock.writeLock();

    public static void read() {
        readLock.lock();
        try {
            System.out.println(Thread.currentThread().getName()+"Got the read lock");
            Thread.sleep(500);
        } catch (InterruptedException e) {
            e.printStackTrace();
        } finally {
            System.out.println(Thread.currentThread().getName()+"Release read lock");
            readLock.unlock();
        }
    }

    public static void write() {
        writeLock.lock();
        try {
            System.out.println(Thread.currentThread().getName() + "Got the write lock");
            Thread.sleep(500);
        } catch (InterruptedException e) {
            e.printStackTrace();
        } finally {
            System.out.println(Thread.currentThread().getName() +"Release write lock");
            writeLock.unlock();
        }
    }

    public static void main(String[] args) {
        new Thread(() -> read()).start();
        new Thread(() -> read()).start();
        new Thread(() -> read()).start();
        new Thread(() -> write()).start();
        new Thread(() -> write()).start();
        new Thread(() -> write()).start();
    }

}

Console printing. From the printing results, it can be seen that the read lock can be obtained by multiple threads at the same time. After one thread obtains the read lock, another thread obtains it again, and the write lock can only be obtained by other threads after the thread is released. Therefore, the read lock is shared and the write lock is exclusive

Thread-0 Got the read lock
Thread-1 Got the read lock
Thread-1 Release read lock
Thread-0 Release read lock
Thread-3 Got the write lock
Thread-3 Release write lock
Thread-2 Got the read lock
Thread-2 Release read lock
Thread-4 Got the write lock
Thread-4 Release write lock
Thread-5 Got the write lock
Thread-5 Release write lock

Lock demotion: refers to the demotion of a write lock to a read lock. The process of obtaining the read lock while holding the current write lock, and then releasing the write lock. Write locks are thread exclusive and read locks are shared. So demoting a write lock to a read lock is demotion. Upgrading a read lock to a write lock cannot be implemented. The example is from the Internet with minor modifications:

import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantReadWriteLock;

public class ReadWriteLockCachedDataExample {

    class Data {}
    class RWDictionary {
        private final Map<String, Data> map = new ConcurrentHashMap<>();
        private final ReentrantReadWriteLock readWriteLock = new ReentrantReadWriteLock();
        private final Lock rLock = readWriteLock.readLock();
        private final Lock wLock = readWriteLock.writeLock();
        public Data get(String key) {
            rLock.lock();
            try {
                return map.get(key);
            }finally {
                rLock.unlock();
            }
        }
        public Data put(String key , Data value) {
            wLock.lock();
            try {
                return map.put(key , value);
            }finally {
                wLock.unlock();
            }
        }
        public String[] allKeys() {
            rLock.lock();
            try {
                return (String[]) map.keySet().toArray();
            }
            finally {
                rLock.unlock();
            }
        }
        public void clear() {
            wLock.lock();
            try {
                map.clear();
            }
            finally {
                wLock.unlock();
            }
        }
    }
    class CachedData {
        Object data;
        volatile boolean cacheValid;
        final ReentrantReadWriteLock rwl = new ReentrantReadWriteLock();

        void processCachedData() {
            rwl.readLock().lock();
            if (!cacheValid) {
                // Must release read lock before acquiring write lock
                rwl.readLock().unlock();
                rwl.writeLock().lock();
                try {
                    // Recheck state because another thread might have
                    // acquired write lock and changed state before we did.
                    if (!cacheValid) {
                        data = getData();
                        cacheValid = true;
                    }
                    // [downgrade] Downgrade by acquiring read lock before releasing write lock
                    rwl.readLock().lock();
                }finally {
                    rwl.writeLock().unlock();
                }
            }

            try {
                use(data);
            } finally {
                rwl.readLock().unlock();
            }
        }

        private Object getData() {
            return null;
        }

        void use(Object data){}
    }

}

Appendix references

Posted by Scott_J on Fri, 03 Dec 2021 22:13:34 -0800