Interview three even

Interviewer: Do you know anything about locks?

Xiaoming: Yes, AND often used.

Interviewer: Tell me the difference between synchronized and lock

Xiaoming: Synchronized is a reentrant lock, because lock is an interface, reentrant depends on the implementation, synchronized does not support interrupts, while lock can……

Interviewer: Ok, is there a faster lock than these two?

Xiaoming: Read/write locks are more efficient than they are when reading more and writing less.

Interviewer: Are there any faster locks than read-write locks?

Xiaoming:…

Oh, my god. Is that what you’re asking? At that time, Xiao Ming was deceived, because synchronized was used more frequently in his project. Read and write lock was rarely used, because it was rarely involved in multi-threading. This interview made him know the importance of multi-threading.

What is a read/write lock

Read/write lock: allows multiple threads to read data at the same time, but only one thread can write data. When a thread obtains the write lock, other write operations and read operations will be blocked. The read lock and write lock are mutually exclusive, so it is not allowed to write data while reading.

See ReentrantReadWriteLock Read/write lock for Java Concurrent Programming.

Read/write locks are much faster than traditional synchronized because they support concurrent reads, while synchronized requires all operations to be serialized. For example, I need to query the basic information of a user, and this information rarely changes, so we store this information in the cache. Our query operation is as follows:

Created with Raphael 2.2.0 Start Query Is there data in the cache? End Add the query database to the cache YESNO

According to the above flow chart, if synchronized is used, query cache will be blocked, but if read/write lock is used, query cache will be concurrent and query database will be blocked. Therefore, read/write lock has significantly better performance than synchronized.

Human civilization is progressing, Java is also progressing, the desire for knowledge is also increasing, so we are constantly thinking about such a question, read and write lock read and write are mutually exclusive, then can we do read and write support concurrency?

;StampedLock was born out of nowhere

StampedLock is an improvement on read-write locks by allowing read and write operations at the same time, which means faster performance than read-write locks.

More generally speaking, a write lock can be acquired when the read lock is not released. After the read lock is acquired, the read lock is blocked, which is the same as the read-write lock. The only difference is that the read-write lock cannot be acquired when the read lock is not released.

StampedLock three modes

  • Pessimistic read: Similar to read-write locks, multiple threads are allowed to acquire pessimistic read locks
  • Write locks: Similar to write locks for read-write locks, write locks and pessimistic reads are mutually exclusive.
  • Optimistic read: Lock-free mechanism, similar to optimistic locks in databases, allows a write lock to be acquired without releasing optimistic reads, unlike read-write locks.

The basic grammar

Let’s first look at the basic syntax for pessimistic read and write locks


        long stamp = lock.readLock();
        try{
            String info = mapCache.get(name);
            if(null! = info){returninfo; }}finally {

            lock.unlock(stamp);
        }

        stamp = lock.writeLock();
        try{

            String info = mapCache.get(name);
            if(null! = info){return info;
            }

            String infoByDb = mapDb.get(name);

            mapCache.put(name,infoByDb);
        }finally {

            lock.unlock(stamp);
        }
Copy the code

StampedLock: long ReentrantReadWriteLock: Lock StampedLock: long ReentrantReadWriteLock: Lock

StampedLock: Unlock (stamp), which requires the value of the long returned by acquiring the lock. ReentrantReadWriteLock: unlock(). You can call unlock.

StampedLock complete demo

package com.ymy.test;

import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.locks.StampedLock;

public class StampedLockTest {

    private static final StampedLock lock = new StampedLock();

    private static Map<String,String> mapCache = new HashMap<String, String>();

    private static Map<String,String> mapDb = new HashMap<String, String>();
    static {
        mapDb.put("zhangsan"."Hi, I'm Joe.");
        mapDb.put("sili"."Hi, I'm Tom.");
    }

    private static String getInfo(String name){

        long stamp = lock.readLock();
        try{
            String info = mapCache.get(name);
            if(null! = info){ System.out.println("Got data in cache");
                returninfo; }}finally {

            lock.unlock(stamp);
        }

        stamp = lock.writeLock();
        try{

            String info = mapCache.get(name);
            if(null! = info){ System.out.println("Get write lock, reconfirm get data in cache");
                return info;
            }

            String infoByDb = mapDb.get(name);

            mapCache.put(name,infoByDb);
            System.out.println("No data in cache, data retrieved in database.");
        }finally {

            lock.unlock(stamp);
        }
        return null;
    }

    public static void main(String[] args) {

        Thread t1 = new Thread(() ->{
            getInfo("zhangsan");
        });

        Thread t2 = new Thread(() ->{
            getInfo("zhangsan");
        });

        t1.start();
        t2.start();

        try {
            t1.join();
            t2.join();
        } catch(InterruptedException e) { e.printStackTrace(); }}}Copy the code

ReentrantReadWriteLock: this is the same as ReentrantReadWriteLock: this is the same as ReentrantReadWriteLock: this is the same as ReentrantReadWriteLock: this is the same as ReentrantReadWriteLock: this is the same as ReentrantReadWriteLock: this is the same as ReentrantReadWriteLock: this is the same as ReentrantReadWriteLock: this is the same as ReentrantReadWriteLock: this is the same as ReentrantReadWriteLock. To see if the lisi thread can successfully acquire the write lock, the code is as follows

package com.ymy.test;

import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.locks.StampedLock;
import java.util.logging.Logger;

public class StampedLockTest {

   private static Logger log = Logger.getLogger(StampedLockTest.class.getName());

    private static final StampedLock lock = new StampedLock();

    private static Map<String,String> mapCache = new HashMap<String, String>();

    private static Map<String,String> mapDb = new HashMap<String, String>();
    static {
        mapDb.put("zhangsan"."Hi, I'm Joe.");
        mapDb.put("sili"."Hi, I'm Tom.");
    }

    private static String getInfo(String name){

        long stamp = lock.readLock();
        log.info("Thread name:"+Thread.currentThread().getName()+"Acquired pessimistic read lock"  +"Username:"+name);
        try{
            if("zhangsan".equals(name)){
                log.info("Thread name:"+Thread.currentThread().getName()+"Dormant"  +"Username:"+name);
                Thread.sleep(3000);
                log.info("Thread name:"+Thread.currentThread().getName()+"End of dormancy"  +"Username:"+name);
            }
            String info = mapCache.get(name);
            if(null! = info){ log.info("Got data in cache");
                returninfo; }}catch (InterruptedException e) {
            log.info("Thread name:"+Thread.currentThread().getName()+"Release pessimistic read lock");
            e.printStackTrace();
        } finally {

            lock.unlock(stamp);
        }

        stamp = lock.writeLock();
        log.info("Thread name:"+Thread.currentThread().getName()+"Got a write lock"  +"Username:"+name);
        try{

            String info = mapCache.get(name);
            if(null! = info){ log.info("Get write lock, reconfirm get data in cache");
                return info;
            }

            String infoByDb = mapDb.get(name);

            mapCache.put(name,infoByDb);
            log.info("No data in cache, data retrieved in database.");
        }finally {

            log.info("Thread name:"+Thread.currentThread().getName()+"Release write lock" +"     用户名:"+name);
            lock.unlock(stamp);
        }
        return null;
    }

    public static void main(String[] args) {

        Thread t1 = new Thread(() ->{
            getInfo("zhangsan");
        });

        Thread t2 = new Thread(() ->{
            getInfo("lisi");
        });

        t1.start();
        t2.start();

        try {
            t1.join();
            t2.join();
        } catch(InterruptedException e) { e.printStackTrace(); }}}Copy the code

If a write lock is acquired during the sleep phase of the zhansan thread, it means that the pessimistic read and write locks are not mutually exclusive, instead, it is mutually exclusive.

March29.2020 11:30:58Morning com. Ymy. Test. StampedLockTest getInfo information: Thread name: Thread -2Got the pessimistic read lock username: Lisi March29.2020 11:30:58Morning com. Ymy. Test. StampedLockTest getInfo information: Thread name: Thread -1Got pessimistic read lock user name: zhangsan March29.2020 11:30:58Morning com. Ymy. Test. StampedLockTest getInfo information: Thread name: Thread -1Hibernating user name: Zhangsan March29.2020 11:31:01Morning com. Ymy. Test. StampedLockTest getInfo information: Thread name: Thread -1End user name: Zhangsan March29.2020 11:31:01Morning com. Ymy. Test. StampedLockTest getInfo information: Thread name: Thread -1The username zhangsan march is obtained29.2020 11:31:01Morning com. Ymy. Test. StampedLockTest getInfo information: the cache without the data, the database access to data in March29.2020 11:31:01Morning com. Ymy. Test. StampedLockTest getInfo information: Thread name: Thread -1Release write lock user name: zhangsan March29.2020 11:31:01Morning com. Ymy. Test. StampedLockTest getInfo information: Thread name: Thread -2Got write lock username: lisi March29.2020 11:31:01Morning com. Ymy. Test. StampedLockTest getInfo information: the cache without the data, the database access to data in March29.2020 11:31:01Morning com. Ymy. Test. StampedLockTest getInfo information: Thread name: Thread -2Release write lock user name: lisiCopy the code

At 11:30:58 lisi and Zhangsan both get pessimistic read lock, and Zhangsan sleep, and then at 11:31:01 sleep end, Zhangsan get write lock, so pessimistic read lock and write lock must be mutually exclusive. Isn’t this as efficient as reading and writing locks? Why is it faster than read-write locks? Isn’t that a contradiction?

Don’t worry guest officer, remember that the best is always at the end, StampedLock mode we only use two of them, there is still one to play, let’s look at optimistic reading.

Make StampedLock performance more optimistic read

Optimistic reading is not a lock, so please do not associate pessimistic reading with pessimistic reading. It is a lockless mechanism, which is equivalent to Java atomic class operations, so performance should theoretically be a little faster than ReentrantReadWriteLock, but not always.

When an optimistic read reads a member variable, the variable needs to be assigned to the local variable, and then determine whether there is a write lock during the program running, if there is, upgrade to pessimistic read.

Let’s take a look at the realization of optimistic reading

package com.ymy.test;

import java.util.concurrent.locks.StampedLock;

public class NumSumTest {

    private static final StampedLock lock = new StampedLock();

    private static int num1 = 1;

    private static int num2 = 1;

    private static int sum(a) {
        System.out.println("The summation method is executed.");

        long stamp = lock.tryOptimisticRead();
        int cnum1 = num1;
        int cnum2 = num2;
        System.out.println("Obtained member variable value,cnum1: + cnum1 + "Cnum2." + cnum2);
        try {

            Thread.sleep(3000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }

        if(! lock.validate(stamp)) { System.out.println("Write exists!");

            stamp = lock.readLock();
            try {
                System.out.println("Upgrade pessimistic read lock");
                cnum1 = num1;
                cnum2 = num2;
                System.out.println("Retrieved the value of the member variable =========== cnum1="+cnum1  +" cnum2="+cnum2);
            } finally{ lock.unlock(stamp); }}return cnum1 + cnum2;
    }

    private static void updateNum(a) {
        long stamp = lock.writeLock();
        try {
            num1 = 2;
            num2 = 2;
        } finally{ lock.unlock(stamp); }}public static void main(String[] args) throws InterruptedException {
        Thread t1 = new Thread(() -> {
            int sum = sum();
            System.out.println("Sum result:" + sum);
        });
        t1.start();

        Thread.sleep(1000);
        updateNum();
        t1.join();
        System.out.println("Executed"); }}Copy the code

Explain the code defines two member variables, then use the t1 thread to compute two member variables and, in order to embody the optimistic reading effect, I am the sum () for 3 seconds, the goal is to make the main the main thread to modify a member variable of value, the main function of sleep is to make the t1 thread can accurately perform to read member variable phase.

Let’s look at the results of the implementation:

The sum method is executed to obtain the value of the member variable,cnum1:1Cnum2:1Write exists! Upgrade pessimistic read lock retrieves the value of the member variable =========== cnum1=2    cnum2=2Sum result:4completedCopy the code

T1 first reads the values of two member variables, and then detects that there is a write operation. That is because the main function uses the write lock to change the values of two member variables. The pessimistic read lock and the write lock are mutually exclusive, and the pessimistic read lock is parallel before, so the optimistic read is upgraded to the pessimistic read lock and the member variable is acquired again to ensure that the data in the current pessimistic read lock is thread-safe.

Do you know the application scenario of optimistic reading

Optimistic reads are not exclusive to StampedLock, but are used in many places, such as optimistic locking pessimistic locking for databases, and atomic class tools for Java concurrency tools.

Mysql: pessimistic lock and optimistic lock

Java concurrent programming: CAS(Compare and Swap)

Precautions for using StampedLock

1.StampedLock is a subclass of ReadWriteLock. ReentrantReadWriteLock is a subclass of ReadWriteLock. As the name suggests, StampedLock does not support reentry locks.

2. It is suitable for reading more and writing less. If not, use it with caution.

3. Pessimistic read and write locks of StampedLock do not support conditional variables.

4. Do not interrupt blocking pessimistic read or write locks. Calling interrupt() on blocking threads will cause CPU spikes. Use readLockInterruptibly (pessimistic read lock) and writeLockInterruptibly (write lock).

conclusion

StampedLock is recommended for over-read and under-write scenarios because its optimistic read performance improves significantly compared with read-write locks. However, use StampedLock with caution in other application scenarios.

Optimistic reads support concurrent write locks, while pessimistic reads and write locks are mutually exclusive, so we can use optimistic reads first. Then determine whether there is a write lock, if there is, you can upgrade the pessimistic read lock, because of the mutual exclusion of the pessimistic read lock and write lock, he can ensure the safety of the thread, if Xiao Ming read my blog more usually, may not be troubled by this problem.

Thank you for watching!