5 minutes to understand - fair lock, unfair lock, reentry lock, spin lock, read-write lock

Fair lock and unfair lock

  • Fair lock
    It refers to that multiple threads acquire locks according to the order of applying for locks, and follow the principle of first come, first serve. Its implementation can use queues.

  • Unfair lock
    The method of multiple threads acquiring locks is not in the order of applying for locks. The thread that applies for locks later can obtain locks earlier than the thread that applies for locks first.

  • The boolean value parameter of ReentrantLock under JUC is the type of lock. true is fair lock and false is unfair lock;
    The code is as follows:

public ReentrantLock() {
    sync = new NonfairSync();
}
public ReentrantLock(boolean fair) {
    sync = fair ? new FairSync() : new NonfairSync();
}

The difference between fair lock and unfair lock

1) When a fair lock obtains a lock in a concurrent environment, it will view the waiting queue maintained by the lock. If the queue is empty or the current thread is the first in the waiting queue, it will occupy the lock. Otherwise, it will join the waiting queue and wait to obtain the lock according to FIFO.
2) An unfair lock directly attempts to occupy the lock. If the attempt fails, it obtains the lock in a way similar to a fair lock.

Note: synchronized is an unfair lock

The difference between synchronized and ReentrantLock

Reentrant lock

  • The locked code can be accessed by the locked thread;

  • After the outer function of the same thread obtains the lock, the inner recursive function can still obtain the code of the lock. When the same thread obtains the lock in the outer method, it will automatically obtain the lock when entering the inner method.

  • The biggest function of reentrant lock is to avoid deadlock;

Code example

/*
 * Reentrant lock (i.e. recursive lock)
 *
 * It refers to the code that the inner recursive function can still obtain the lock after the outer function of the same thread obtains the lock,
 * When the same thread acquires the lock in the outer method, it will automatically acquire the lock when entering the inner method.
 *
 * In other words, a thread can enter any lock it already has and all synchronized code blocks.
 *
 * t1   invoked sendSMS()      t1 When a thread acquires a lock in an outer method
 * t1   invoked sendEmail()    t1 When entering the inner layer, the method will automatically obtain the lock
 * t2   invoked sendSMS()
 * t2   invoked sendEmail()
 *
 */
public class RenenterLockDemo {
    public static void main(String[] args) {
        Phone phone = new Phone();
        new Thread(() -> {
            try {
                phone.sendSMS();
            } catch (Exception e) {
                e.printStackTrace();
            }
        }, "t1").start();

        new Thread(() -> {
            try {
                phone.sendSMS();
            } catch (Exception e) {
                e.printStackTrace();
            }
        }, "t2").start();
    }

}

class Phone {
    public synchronized void sendSMS() throws Exception {
        System.out.println(Thread.currentThread().getName() + "\t invoked sendSMS()");
        sendEmail();
    }

    public synchronized void sendEmail() throws Exception {
        System.out.println(Thread.currentThread().getName() + "\t invoked sendEmail()");
    }
}

It can be proved that synchronized is a reentrant lock; After entering the sendSMS method, you can also access another synchronization method sendEmail;
It is also proved that the current instance object of the synchronized lock;

package com.juc;

import java.util.concurrent.locks.ReentrantLock;

public class ReentryLock {
    public static void main(String[] args) {
        Phone phone=new Phone();
        new Thread(()->{
            phone.set();
        },"AAA").start();
        new Thread(()->{
            phone.set();
        },"BBB").start();
    }
}
class Phone{
    public ReentrantLock lock=new ReentrantLock();
    public void set(){
        lock.lock();
        try{
            System.out.println("set.....");
            get();
        }catch(Exception e){

        } finally{
            lock.unlock();
        }
    }
    public void get(){
        lock.lock();
        try{
            System.out.println("get....");
        }catch(Exception e){

        } finally{
            lock.unlock();
        }
    }
}

It can be proved that ReentrantLock is a reentrant lock; After entering the synchronization code of set, you can also access the code block in another get method;

Spin lock

The thread trying to acquire the lock will not block immediately, but wait for the lock in a circular way. Instead of being suspended at the operating system level (user mode - "kernel mode"), context switching is avoided.
Scenario: the thread takes a short time to execute the task, and the lock can be released quickly. The lock can be obtained during cyclic waiting.

public final int getAndAddInt(Object var1, long var2, int var4) {
    int var5;
    do {
        var5 = this.getIntVolatile(var1, var2);
    } while(!this.compareAndSwapInt(var1, var2, var5, var5 + var4));

    return var5;
}

CAS is involved here;

Spin lock code example

The Thread is encapsulated by atomic reference, and the spin lock of the Thread is realized by CAS algorithm

/**
 * Write a spin lock
 * Benefits of spin lock: the cyclic comparison is obtained until it is successful, and there is no blocking like wait.
 *
 * Complete spin lock through CAS operation:
 *  A The thread first calls the myLock method and holds the lock for 5 seconds
 *  B Then I came in and found that a thread currently holds a lock, which is not null,
 *  Therefore, we can only wait by spinning until A releases the lock and then B grabs it
 *
 * @ClassName SpinLockDemo
 * @Description TODO
 * @Author Heygo
 * @Date 2020/8/8 13:37
 * @Version 1.0
 */
public class SpinLockDemo {
    // The generic type is Thread
    AtomicReference<Thread> atomicReference = new AtomicReference<>();

    public void myLock() {
        // Get current thread
        Thread thread = Thread.currentThread();
        System.out.println(Thread.currentThread().getName() + "\t come in ");
        /*
         Spin:
            A null expectation indicates that there are currently no threads
            The new value is thread, which is thread currentThread()
          */
        while (!atomicReference.compareAndSet(null, thread)) {

        }
    }

    public void myUnLock() {
        // Get current thread
        Thread thread = Thread.currentThread();
        // Unlock current thread
        atomicReference.compareAndSet(thread, null);
        System.out.println(Thread.currentThread().getName() + "\t invoked myUnLock()");
    }

    public static void main(String[] args) {
        // Atomic reference thread
        SpinLockDemo spinLockDemo = new SpinLockDemo();

        new Thread(() -> {
            spinLockDemo.myLock(); // Lock
            try {
                TimeUnit.SECONDS.sleep(5);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            spinLockDemo.myUnLock(); // Unlock
        }, "AA").start();

        try {
            TimeUnit.SECONDS.sleep(1);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }

        new Thread(() -> {
            spinLockDemo.myLock(); // Lock
            try {
                TimeUnit.SECONDS.sleep(1);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            spinLockDemo.myUnLock(); // Unlock
        }, "BB").start();
    }
}
Program running results: the core is CAS algorithm
 thread  A Execute first, and the expected value is null ,thread  A Will acquire the lock and set the expected value to thread A oneself
 thread  B Try to acquire the lock and find that the expected value is not null ,Right there, spin in place
 thread  A After releasing the lock, set the expected value to null ,Thread at this time B Obtain the lock and set the expected value to thread B oneself
 Last thread B Release lock 3


Exclusive lock (write lock), shared lock (read lock), mutex lock

  • Exclusive lock: it means that the lock can only be held by one thread at a time. Both ReentrantLock and synchronized are exclusive locks.
  • Shared lock: the lock can be held by multiple threads.
  • ReentrantWriteReadLock: the internal read lock is a shared lock and the write lock is an exclusive lock;
public class ReadWriteLockTest {
    public static void main(String[] args) {
        MyCache myCache=new MyCache();
        for (int i = 0; i < 5; i++) {
            final int index=i;
            new Thread(()->{
                myCache.put(String.valueOf(index),index);
            },String.valueOf(i)).start();
        }
        for (int i = 0; i < 5; i++) {
            final int index=i;
            new Thread(()->{
                myCache.get(String.valueOf(index));
            },String.valueOf(i)).start();
        }

    }
}
class MyCache{
    public volatile Map<String,Object> map=new HashMap<>();
    ReentrantReadWriteLock lock=new ReentrantReadWriteLock();
    public void put(String key,Object value){
        lock.writeLock().lock();
        try{
            System.out.println(Thread.currentThread().getName() + "\t Writing:" + key);
            Thread.sleep(3000);
            map.put(key,value);
            System.out.println(Thread.currentThread().getName() + "\t Write complete");
        }catch(Exception e){

        } finally{
            lock.writeLock().unlock();
        }
    }
    public void get(String key){

        lock.readLock().lock();
        try{
            System.out.println(Thread.currentThread().getName()+"read---");
            Object val=map.get(key);
            System.out.println(Thread.currentThread().getName()+"End of reading result="+val);
        }catch(Exception e){

        } finally{
            lock.readLock().unlock();
        }
    }
}


Program running result: the write operation is not interrupted, and the read operation can be carried out at the same time

1	 Writing: 1
1	 Write complete
2	 Writing: 2
2	 Write complete
4	 Writing: 4
4	 Write complete
3	 Writing: 3
3	 Write complete
0 read---
0 End of reading result=0
1 read---
1 End of reading result=1
2 read---
3 read---
3 End of reading result=3
4 read---
2 End of reading result=2
4 End of reading result=4

ReentrantReadWriteLock source code

public class ReentrantReadWriteLock
        implements ReadWriteLock, java.io.Serializable {
    private static final long serialVersionUID = -6992448646407690164L;
    /** Inner class providing readlock */
    private final ReentrantReadWriteLock.ReadLock readerLock;
    /** Inner class providing writelock */
    private final ReentrantReadWriteLock.WriteLock writerLock;

A read lock and a write lock are maintained respectively;

In addition, Synchronized involves some locks, biased locks, lightweight locks and heavyweight locks in the process of lock upgrading.
You can view this blog post
10 minutes to understand - in-depth understanding of the Synchronized keyword

Keywords: Java Multithreading queue Operating System Concurrent Programming

Added by balloontrader on Mon, 31 Jan 2022 12:53:02 +0200