This chapter route general outline: no lock, exclusive lock, read and write lock, postmark lock. The interview questions about lock are as follows:

  • What locks do you know in Java?
  • You say you’ve used read-write locks. What’s the problem with lock hunger?
  • Are there any faster locks than read-write locks?
  • Does StampedLock know? (Postmark lock/Ticket lock)
  • Did you know that ReentrantReadWriteLock has a lock degradation policy?

ReentrantReadWriteLock

A read/write lock is defined as a resource that can be accessed by multiple reader threads, or by one writer thread, but that cannot exist simultaneously.Two sides of the same body,Read and write are mutually exclusive, read and shareBlade and blade repel each other.

The read/write lock ReentrantReadWriteLock does not allow read/write separation. Read/write is mutually exclusive.

In most practical scenarios, mutual exclusion does not exist between read/read threads, only operations between read/write threads or between write/write threads need to be mutually exclusive. Hence the introduction of ReentrantReadWriteLock.

A ReentrantReadWriteLock can have only one write lock but can have multiple read locks. A ReentrantReadWriteLock cannot have both write locks and read locks. That is, a resource can be accessed by multiple read operations or one write operation, but not both at the same time.

Read/write locks perform well only in read/write scenarios.

Simple code for read/write locks is shown below:

class MyResource
{
    Map<String,String> map = new HashMap<>();
    =====ReentrantLock is equivalent to =====synchronized
    Lock lock = new ReentrantLock();
    //=====ReentrantReadWriteLock has two sides. Read and write are mutually exclusive and shared
    ReentrantReadWriteLock rwLock = new ReentrantReadWriteLock();

    public void write(String key,String value)
    {
        rwLock.writeLock().lock();
        try
        {
            System.out.println(Thread.currentThread().getName()+"\t"+"-- writing");
            map.put(key,value);
            try { TimeUnit.MILLISECONDS.sleep(500); } catch (InterruptedException e) { e.printStackTrace(); }
            System.out.println(Thread.currentThread().getName()+"\t"+"-- Complete write");
        }finally{ rwLock.writeLock().unlock(); }}public void read(String key)
    {
        rwLock.readLock().lock();
        try
        {
            System.out.println(Thread.currentThread().getName()+"\t"+"-- reading now");
            String result = map.get(key);
            // Pause the thread for a few seconds
            try { TimeUnit.SECONDS.sleep(2); } catch (InterruptedException e) { e.printStackTrace(); }
            System.out.println(Thread.currentThread().getName()+"\t"+"-- finish reading result:"+result);
        }finally{ rwLock.readLock().unlock(); }}}/ * * *@auther zzyy
 * @createThe 2021-03-28 11:04 * /
public class ReentrantReadWriteLockDemo
{
    public static void main(String[] args)
    {
        MyResource myResource = new MyResource();

        for (int i = 1; i <=10; i++) {
            int finalI = i;
            new Thread(() -> {
                myResource.write(finalI +"", finalI +"");
            },String.valueOf(i)).start();
        }

        for (int i = 1; i <=10; i++) {
            int finalI = i;
            new Thread(() -> {
                myResource.read(finalI +"");
            },String.valueOf(i)).start();
        }

        for (int i = 1; i <=3; i++) {
            int finalI = i;
            new Thread(() -> {
                myResource.write(finalI +"", finalI +"");
            },"Ma Yu Cheng"+String.valueOf(i)).start(); }}}Copy the code

From write lock to read lock, ReentrantReadWriteLock can be degradedThe art of Concurrent Programming in Java explains lock degradation as follows: A tighter lock is called an upgrade, and a lower lock is called a downgrade

Lock degradation: To degrade a write lock to a read lock.

Lock degradation: A write lock can be degraded to a read lock in the order of acquiring a write lock, then acquiring a read lock, and then releasing a write lock.

If a thread possesses a write lock, it can also possess a read lock without releasing the write lock, which is degraded to a read lock.

Reentrant also allows you to go from a write lock to a read lock by acquiring a write lock, then reading the lock and then releasing the write lock. However, upgrading from a read lock to a write lock is not possible.

Lock degradation is intended to make the current thread aware of changes to the data to ensure data visibility

LockDownGradingDemo code is as follows:

package com.atguigu.juc.rwlock;

import java.util.concurrent.locks.ReentrantReadWriteLock;

/ * * *@auther zzyy
 * @createLock degradation: A write lock can be degraded to a read lock in the sequence of acquiring a write lock, then acquiring a read lock, and then releasing a write lock. * * If a thread owns a write lock, it can also possess a read lock without releasing the write lock, i.e. the write lock is degraded to a read lock. * /
public class LockDownGradingDemo
{
    public static void main(String[] args)
    {
        ReentrantReadWriteLock readWriteLock = new ReentrantReadWriteLock();

        ReentrantReadWriteLock.ReadLock readLock = readWriteLock.readLock();
        ReentrantReadWriteLock.WriteLock writeLock = readWriteLock.writeLock();


        writeLock.lock();
        System.out.println("------- writing");


        readLock.lock();
        System.out.println("------- reading"); writeLock.unlock(); }}Copy the code

If a thread is reading, the writer thread cannot acquire the write lock, which is a pessimistic locking strategy

However, there is no lock upgrade, and the thread acquiring the read lock cannot be directly upgraded to the write lock.In ReentrantReadWriteLock, if a thread attempts to acquire a write lock while a read lock is in use, the writer thread is blocked. Therefore, all read locks need to be released in order to acquire write locks.Lock upgrade error code is shown as follows:

package com.atguigu.juc.rwlock;

import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.ReentrantReadWriteLock;

/ * * *@auther zzyy
 * @createLock degradation: A write lock can be degraded to a read lock in the sequence of acquiring a write lock, then acquiring a read lock, and then releasing a write lock. * * If a thread owns a write lock, it can also possess a read lock without releasing the write lock, i.e. the write lock is degraded to a read lock. * /
public class LockDownGradingDemo
{
    public static void main(String[] args)
    {
        ReentrantReadWriteLock readWriteLock = new ReentrantReadWriteLock();

        ReentrantReadWriteLock.ReadLock readLock = readWriteLock.readLock();
        ReentrantReadWriteLock.WriteLock writeLock = readWriteLock.writeLock();

        // There is only one thread, main, to verify the lock degradation policy requirements
        readLock.lock();
        System.out.println("-----read");
        // Pause the thread for a few seconds
        try { TimeUnit.SECONDS.sleep(2); } catch (InterruptedException e) { e.printStackTrace(); }
        writeLock.lock();
        System.out.println("-- -- -- -- -- 1111");
        writeLock.unlock();
        readLock.unlock();
        System.out.println("-- -- -- -- -- 2222"); }}Copy the code

The above code causes the thread to never acquire the write lock, resulting in blocking.Write and read locks are mutually exclusive (The mutual exclusion here refers to the mutual exclusion between threads. The current thread can obtain both the write lock and the read lock, but after obtaining the read lock, it cannot continue to obtain the write lock), because read/write locks keep write operations visible.

Because if a read lock is allowed to acquire a write lock while it is being acquired, the current writer thread cannot be sensed by other running reader threads.

Therefore, ReentrantReadWriteLock has a potential problem: The read lock is complete, but the write lock is expected. Write lock exclusive, read and write full block;

If a thread is reading data, the writer thread must wait for the reader thread to release the lock before acquiring the write lock. In this Case, the ReadWriteLock is not allowed to write data. The current thread can acquire the write lock only when the waiting thread releases the read lock.

That is, write must wait, this is a pessimistic read lock, O (╥﹏╥) O, someone else is still reading that, you don’t go to write, save the data chaos.

Oracle reentrantwitereadlock source code summary as follows:

ReentrantWriteReadLock supports lock degradation by obtaining write locks, obtaining read locks, and releasing write locks. Write locks can be degraded to read locks, but lock escalation is not supported.

The reading is at the bottom:A volatile cacheValid variable is declared to ensure its visibility.

2 Obtain a read lock. If the cache is unavailable, release the read lock and obtain the write lock. Before changing data, check the value of cacheValid again, set cacheValid to true, and obtain the read lock before releasing the write lock. At this point, the cache data is available, the cache data is processed, and the read lock is released. This process is a complete lock degradation process to ensure data visibility.

If the procedure of lock degradation is violated, if the current thread C does not acquire the read lock but directly releases the write lock after modifying the data in the cache, then assuming that another thread D obtains the write lock and modifs the data, thread C cannot perceive that the data has been modified, and data errors occur.

If thread C follows the steps of lock degradation to acquire the read lock before releasing the write lock, thread D will be blocked while acquiring the write lock until thread C completes its data processing and releases the read lock. This ensures that the data returned is the same as the updated data, a mechanism designed specifically for caching.

Interview question: Is there a faster lock than a read-write lock? Answer: Postmark lock StampedLock

The postmark lock StampedLock

The evolution process of lock is as follows: no lock → exclusive lock → read-write lock → postmark lock.

StampedLock (also called paper lock postmark lock) is the new JDK1.8 a read-write lock, and the optimization of the read-write lock ReentrantReadWriteLock JDK1.5.

StampedLock represents the lock state through the variable stamp (stamp, type long). When stamp returns zero, it indicates that the thread failed to acquire the lock. In addition, when a lock is released or converted, the stamp value originally obtained is passed in.

ReentrantReadWriteLock implements read/write isolation. However, when a large number of reads are performed, it is difficult to obtain a write lock. If the current 1000 threads have 999 reads and 1 write, it is possible that 999 readers will hold the lock for a long time. That one writer thread is a tragedy. Because currently there may always be a read lock, and cannot get a write lock, there is no chance to write, o(╥﹏╥)o

How can lock hunger be alleviated? This problem can be mitigated to some extent by using a “fair” policy, but the “fair” policy comes at the expense of system throughput.Here comes the optimistic read lock of the StampedLock class.

ReentrantReadWriteLock allows multiple threads to read data simultaneously but allows only one thread to write data. When a thread obtains a write lock, other write operations are blocked. The read lock and write lock are mutually exclusive. Read/write locks are much faster than traditional synchronized because ReentrantReadWriteLock supports read concurrency

When the read lock on ReentrantReadWriteLock is occupied, other threads attempting to acquire the write lock are blocked. However, if StampedLock adopts optimistic lock acquisition, other threads will not be blocked when trying to acquire write locks. This is actually an optimization of read locks. Therefore, after optimistic read locks are acquired, the results need to be verified.

The characteristics of StampedLock

  • All lock acquisition methods, return a postmark (Stamp), Stamp is zero to obtain failed, the rest are successful;
  • All lock release methods require a postmark (Stamp), which must be the same as the Stamp obtained when the lock was successfully obtained;
  • StampedLock is non-reentrant and dangerous (if a thread already holds a write lock, trying to acquire it will cause a deadlock)

StampedLock has three access modes

  • ①Reading (Read mode) : This function is similar to that of ReentrantReadWriteLock
  • ②Writing mode: It has the same functionality as ReentrantReadWriteLock
  • ③Optimistic Reading (Optimistic reading mode) : no lock mechanism, similar to the Optimistic lock in the database, support read and write concurrency, very Optimistic that no one will modify when reading, if modified to achieve the upgrade to pessimistic reading mode

Optimistic read mode code is shown below:

package com.atguigu.itdachang;

import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.StampedLock;

/ * * *@auther zzyy
 * @createThe 2020-07-22 16:03 * /
public class StampedLockDemo
{
    static int number = 37;
    static StampedLock stampedLock = new StampedLock();

    public void write(a)
    {
        long stamp = stampedLock.writeLock();
        System.out.println(Thread.currentThread().getName()+"\t"+"===== writer thread ready to change");
        try
        {
            number = number + 13;
        }catch (Exception e){
            e.printStackTrace();
        }finally {
            stampedLock.unlockWrite(stamp);
        }
        System.out.println(Thread.currentThread().getName()+"\t"+"===== writer-end modification");
    }

    / / gloomy reading
    public void read(a)
    {
        long stamp = stampedLock.readLock();
        System.out.println(Thread.currentThread().getName()+"\t come in readlock block,4 seconds continue...");
        // Pause the thread for a few seconds
        for (int i = 0; i <4 ; i++) {
            try { TimeUnit.SECONDS.sleep(1); } catch (InterruptedException e) { e.printStackTrace(); }
            System.out.println(Thread.currentThread().getName()+"\t reading......");
        }
        try
        {
            int result = number;
            System.out.println(Thread.currentThread().getName()+"\t"+"Get member variable value result:" + result);
            System.out.println("The writer thread did not modify the value because stampedlock. readLock() cannot be read, and reads and writes are mutually exclusive.");
        }catch (Exception e){
            e.printStackTrace();
        }finally{ stampedLock.unlockRead(stamp); }}/ / optimistic reading
    public void tryOptimisticRead(a)
    {
        long stamp = stampedLock.tryOptimisticRead();
        int result = number;
        // The interval is 4 seconds. We are optimistic that no other thread has changed the number value.
        System.out.println("4 seconds ago stampedlock. validate value (true unchanged, false modified)"+"\t"+stampedLock.validate(stamp));
        for (int i = 1; i <=4 ; i++) {
            try { TimeUnit.SECONDS.sleep(1); } catch (InterruptedException e) { e.printStackTrace(); }
            System.out.println(Thread.currentThread().getName()+"\t reading......"+i+
                    "Stampedlock. validate value after seconds (true unchanged, false modified)"+"\t"
                    +stampedLock.validate(stamp));
        }
        if(! stampedLock.validate(stamp)) { System.out.println("Someone moved -------- a write operation exists!");
            stamp = stampedLock.readLock();
            try {
                System.out.println("Upgrade from optimistic reading to pessimistic reading");
                result = number;
                System.out.println("Re-pessimistic read lock obtained by member variable value result:" + result);
            }catch (Exception e){
                e.printStackTrace();
            }finally {
                stampedLock.unlockRead(stamp);
            }
        }
        System.out.println(Thread.currentThread().getName()+"\t finally value: "+result);
    }

    public static void main(String[] args)
    {
        StampedLockDemo resource = new StampedLockDemo();

        new Thread(() -> {
            resource.read();
            //resource.tryOptimisticRead();
        },"readThread").start();

        / / 2 seconds when optimism read failure, 6 seconds optimistic reads the resource. Successful tryOptimisticRead (); Modify the switch demo
        //try { TimeUnit.SECONDS.sleep(6); } catch (InterruptedException e) { e.printStackTrace(); }

        new Thread(() -> {
            resource.write();
        },"writeThread").start(); }}Copy the code

The read process also allows access to write locks

The disadvantage of StampedLock

  • StampedLock does not support reentry and does not start with an Re
  • StampedLock’s pessimistic read and write locks do not support Condition variables, which should also be noted.
  • StampedLock must not call interrupt(). If you want interrupt support, use the pessimistic interruptible read lock readLockInterruptibly() and write lock writeLockInterruptibly().

Remember: It’s best not to use StampedLock at work. Use It for interviews. Use ReentrantLock and ReentrantReadWriteLock at work