[concurrent programming] (learning notes – JUC of sharing model – principle of read-write lock) – part6

Time:2022-1-21

Read write lock

1.ReentrantReadWriteLock

1.1 general

When the read operation is much higher than the write operation, the read-write lock is used to make theRead readCan be concurrent and improve performance. Similar to in databaseselect ... from ... lock in share mode

Chestnuts:

A data container class is provided, which uses the read () method for reading lock protection data and the write () method for writing lock protection data

@Slf4j
class Datacontainer {
    private Object data;
    private ReentrantReadWriteLock rw = new ReentrantReadWriteLock();
    private ReentrantReadWriteLock.ReadLock r = rw.readLock();
    private ReentrantReadWriteLock.WriteLock w = rw.writeLock();

    public Object read() {
        r.lock();
        log. Debug ("get read lock..);
        try {
            log. Debug ("read");
            Sleeper.sleep(1);
            return data;
        } finally {
            log. Debug ("release read lock...);
            r.unlock();
        }
    }

    public void write() {
        w.lock();
        log. Debug ("get write lock...);
        try {
            log. Debug ("write");
            Sleeper.sleep(1);
        } finally {
            log. Debug ("release write lock...);
            w.unlock();
        }
    }
}

testRead lock - read lockCan be concurrent:

public class TestReadWriteLock {
    public static void main(String[] args) {
        Datacontainer datacontainer = new Datacontainer();
        new Thread(() -> {
            datacontainer.read();
        }, "t1").start();
        new Thread(() -> {
            datacontainer.read();
        }, "t2").start();
    }
}

The output result shows that during thread-0 locking, the read operation of thread-1 is not affected:

[concurrent programming] (learning notes - JUC of sharing model - principle of read-write lock) - part6

testRead lock write lockMutual blocking:

@Slf4j
public class TestReadWriteLock {
    public static void main(String[] args) throws InterruptedException {
        Datacontainer datacontainer = new Datacontainer();
        new Thread(() -> {
            datacontainer.read();
        }, "t1").start();
        Thread.sleep(100);
        new Thread(() -> {
            datacontainer.write();
        }, "t2").start();
    }
}

result:

[concurrent programming] (learning notes - JUC of sharing model - principle of read-write lock) - part6

be careful:

  • Read lock does not support conditional variables
  • Upgrade during reentry is not supported: that is, obtaining a write lock while holding a read lock will cause permanent waiting for obtaining a write lock
  • Downgrade support on reentry: to obtain a read lock while holding a write lock

Look at a reentry demotion to get lock chestnuts:

class CachedData {
    Object data;
    //Is it valid? If it fails, recalculate the data
    volatile boolean cacheValid;
    final ReentrantReadWriteLock rwl = new ReentrantReadWriteLock();
    void processCachedData() {
        rwl.readLock().lock();
        if (!cacheValid) {
            //The read lock must be released before acquiring the write lock
            rwl.readLock().unlock();
            rwl.writeLock().lock();
            try {
                //Judge whether other threads have obtained the write lock and updated the cache to avoid repeated updates
                if (!cacheValid) {
                    data = ...
                        cacheValid = true;
                }
                //Demote to read lock and release the write lock, so that other threads can read the cache
                rwl.readLock().lock();
            } finally {
                rwl.writeLock().unlock();
            }
        }
        //When you run out of data, release the read lock 
        try {
            use(data);
        } finally {
            rwl.readLock().unlock();
        }
    }
}

1.2 principle demonstration

The read-write lock uses the same Sycn synchronizer, so the waiting queue and state are the same.

t1 w.lock,t2 r.lock

1) T1 locks successfully. The process is no different from reentrantlock locking. The difference isThe write lock status occupies the lower 16 bits of state, while the read lock uses the upper 16 bits of state

[concurrent programming] (learning notes - JUC of sharing model - principle of read-write lock) - part6

2) T2 execute RlockAt this time, enter syncacquireShared(1) Process, first entertryAcquireSharedtechnological process. If a write lock is occupied, tryacquireshared returns – 1, indicating failure

The return value of tryacquireshared indicates:

  • -1 means failure
  • 0 indicates success
  • A positive number indicates success (and the value is that several subsequent nodes need to wake up, and the read-write lock returns 1)

[concurrent programming] (learning notes - JUC of sharing model - principle of read-write lock) - part6

3) You will enter syncdoAcquireShared(1) Process is also called firstaddWaiterAdd a node, except that the node is set toNode.SHAREDMode instead of node Exclusive mode, note that T2 is still active at this time

[concurrent programming] (learning notes - JUC of sharing model - principle of read-write lock) - part6

4) T2 will check whether its node is the second. If so, it will call againtryAcquireShared(1) To try to acquire the lock

5) If not, in doacquiresharedfor (;;)Loop once, change the waitstatus of the precursor node to – 1, and then for (; loop once to try to acquire shared (1). If it is not successful, clickparkAndCheckInterrupt() park at

[concurrent programming] (learning notes - JUC of sharing model - principle of read-write lock) - part6

t3 r.lock,t4 w.lock

In this state, suppose T3 adds a read lock and T4 adds a write lock. During this period, T1 still holds the lock, which becomes the following

[concurrent programming] (learning notes - JUC of sharing model - principle of read-write lock) - part6

t1 w.unlock

At this time, you will go to syncrelease(1) Process, call synctryRelease(1) Success, become the following

[concurrent programming] (learning notes - JUC of sharing model - principle of read-write lock) - part6

Next, execute the wake-up process syncunparkSuccessorThat is, let the second run again. At this time, T2 isdoAcquireSharedwithinparkAndCheckInterruptIt’s running again this timefor (;;)implementtryAcquireSharedIf successful, the read lock count is incremented by one

[concurrent programming] (learning notes - JUC of sharing model - principle of read-write lock) - part6

At this time, T2 has resumed operation, and then T2 callssetHeadAndPropagate(node, 1), the original node is set as the head node

[concurrent programming] (learning notes - JUC of sharing model - principle of read-write lock) - part6

It’s not over yetsetHeadAndPropagateMethod also checks whether the next node isshared, if yes, calldoReleaseShared() change the state of head from – 1 to 0 and wake up the second. At this time, T3 isdoAcquireSharedwithinparkAndCheckInterruptResume operation at

[concurrent programming] (learning notes - JUC of sharing model - principle of read-write lock) - part6

Do it again this timefor (;;)implementtryAcquireSharedIf successful, the read lock count is incremented by one

[concurrent programming] (learning notes - JUC of sharing model - principle of read-write lock) - part6

At this time, T3 has resumed operation. Next, T3 callssetHeadAndPropagate(node, 1), the original node is set as the head node

[concurrent programming] (learning notes - JUC of sharing model - principle of read-write lock) - part6

Next node is notsharedTherefore, the node where T4 is located will not continue to wake up

t2 r.unlock,t3 r.unlock

T2 enter syncreleaseShared(1) calltryReleaseShared(1) Let the count decrease by one, but since the count is not yet zero

[concurrent programming] (learning notes - JUC of sharing model - principle of read-write lock) - part6

T3 enter syncreleaseShared(1) calltryReleaseShared(1) Let the count decrease by one. This time the count is zero. EnterdoReleaseShared() change the header node from – 1 to 0 and wake up the second, that is

[concurrent programming] (learning notes - JUC of sharing model - principle of read-write lock) - part6

Then T4 atacquireQueuedinparkAndCheckInterruptResume operation at and restart againfor (;;)This time I’m the second and there’s no other competition,tryAcquire(1) Successfully, modify the header node, and the process ends

[concurrent programming] (learning notes - JUC of sharing model - principle of read-write lock) - part6

2.StampedLock

2.1 general

This class is added from JDK 8 to further optimize the read performance. Its feature is that when using read lock and write lock, it must be used with [stamp]

Lock:

long stamp = lock.readLock();
lock.unlockRead(stamp);

Add / remove write lock:

long stamp = lock.writeLock();
lock.unlockWrite(stamp);

Optimistic readingStampedLocksupporttryOptimisticRead()Method (reading through music). After reading, you need to perform a stamp verification. If the verification passes, it means that there is no write operation during this period, and the data can be used safely. If the verification fails, you need to obtain the read lock again to ensure the data security.

long stamp = lock.tryOptimisticRead();
//Check stamp
if(!lock.validate(stamp)){
 //Lock upgrade
}

2. Examples

A data container class is provided, which uses the read () method of reading lock protection data and the write () method of writing lock protection data respectively:

@Slf4j
class DataContainerStamper {
    private int data;
    private final StampedLock lock = new StampedLock();

    public DataContainerStamper(int data) {
        this.data = data;
    }

    public int read(int readTime) {
        //Optimistic reading
        long stamp = lock.tryOptimisticRead();
        log.debug("optimistic read lockuing ... {}", stamp);
        //Analog read time
        Sleeper.sleep(readTime);
        //Verification stamp, returned directly by
        if (lock.validate(stamp)) {
            log.debug("read finish... {}", stamp);
            return data;
        }
        //Lock upgrade - > read lock
        log.debug("updating to read lock... {}", stamp);
        try {
            //Acquire read lock
            stamp = lock.readLock();
            log.debug("read lock {}", stamp);
            Sleeper.sleep(readTime);
            log.debug("read finish... {}", stamp);
            return data;
        } finally {
            log.debug("read unlock {}", stamp);
            //Release read lock
            lock.unlockRead(stamp);
        }

    }

    public void write(int newData) {
        //Write lock
        long stamp = lock.writeLock();
        log.debug("write lock {}", stamp);
        try {
            Sleeper.sleep(2);
            this.data = newData;
        } finally {
            log.debug("write unlock {}", stamp);
            //Releasing the write lock requires an incoming stamp
            lock.unlockWrite(stamp);
        }
    }
}

testRead readCan optimize:

public class TestStampedLock {
    public static void main(String[] args) {
        DataContainerStamper dataContainer = new DataContainerStamper(1);
        new Thread(() -> {
            dataContainer.read(1);
        }, "t1").start();
        Sleeper.sleep(0.5);
        new Thread(() -> {
            dataContainer.read(0);
        }, "t2").start();
    }
}

According to the output result, it can be seen that there is no read lock:

[concurrent programming] (learning notes - JUC of sharing model - principle of read-write lock) - part6

testRead writeOptimize read supplement and add read lock when:

public class TestStampedLock {
    public static void main(String[] args) {
        DataContainerStamper dataContainer = new DataContainerStamper(1);
        new Thread(() -> {
            dataContainer.read(1);
        }, "t1").start();
        Sleeper.sleep(0.5);
        new Thread(() -> {
            dataContainer.write(0);
        }, "t2").start();
    }
}

Output results:

[concurrent programming] (learning notes - JUC of sharing model - principle of read-write lock) - part6

be careful:

  • Stampedlock does not support conditional variables
  • Stampedlock does not support reentry

[concurrent programming] (learning notes - JUC of sharing model - principle of read-write lock) - part6

Recommended Today

2021-11-09 volcano map based on RNA SEQ table (second time)

setwd(“C:\\Users\\Administrator.DESKTOP-4UQ3Q0K\\Desktop”) library(“readxl”) data <- read_excel(“RNA-seq.xlsx”) library(dplyr) library(ggplot2) library(ggrepel) data #Convert to tibble for subsequent use and remove unnecessary columns; Data < – Data [C (- 10, – 11, – 14, – 15, – 16, – 19, – 20, – 21, – 22)] # don’t try #data <- as_tibble(data[c(-10,-11,-14,-15,-16,-19,-20,-21,-22)]) data$padj<-as.numeric(as.matrix(data$padj)) #Take logarithm of Q value; data$log10FDR […]