Understanding of Java locks

Fair lock and unfair lock

     Friends who have used concurrent packages should not be unfamiliar with this. ReentrantLock is a reentrant lock provided in Java's JUC (java.util.concurrent) package, which is a recursive non-blocking synchronization mechanism. ReentrantLock is equivalent to the synchronized keyword, but ReentrantLock provides a more powerful and flexible locking mechanism than synchronized, which can reduce the probability of deadlocks

unfair lock

    ReentrantLock also provides fair and unfair locks. An unfair lock is when the lock is released, all waiting threads have the opportunity to acquire the lock. It is possible that the thread that applies later acquires the lock first than the thread that applies first. In the case of high concurrency, it may cause priority inversion or starvation. synchronized is an unfair lock, and ReentrantLock is the same. But ReentrantLock can receive an optional fair parameter through the constructor (default is unfair lock)

fair lock

The so-called fair lock means that the lock acquisition strategy is relatively fair. When multiple threads acquire the same lock, they must acquire the lock at one time according to the lock application time, that is, in order.


When the incoming value is true, it means it is a fair lock. The source code is as follows:

About the difference between fair lock and unfair lock

  • Fair lock: It is very fair. In a concurrent environment, each thread will first check the waiting queue maintained by the lock when acquiring the lock. If it is empty, or when the thread is the first in the waiting queue, it will own the lock. Otherwise, it will be added to the waiting queue, and will be obtained from the queue according to the FIFO (first-in, first-out principle) in the future.
  • Unfair locks: Unfair locks are rude. When you come up, try to own the lock. If the attempt fails, you will use a method similar to fair locks.

For ReentrantLock, whether the lock is a fair lock is specified through the constructor, and the default is an unfair lock. The advantage of unfair locks is that the throughput is greater than fair locks

For synchronized, it is also an unfair lock

Reentrant locks (also called recursive locks)

It means that after the outer function of the same thread acquires the lock, the inner recursive function can still acquire the code of the lock. When the same thread acquires the lock in the outer method, it will automatically acquire the lock when entering the inner method.

Simply put, a thread can enter any block of code that is synchronized by a lock it already owns

Example

package concurrency.day0411;

/**
 * @author Woo_home
 * @create by 2020/4/11  20:06
 */

// Thread manipulation resource class
class ThreadDemo {
	// define a synchronized method
    public synchronized void seyHello() {
        System.out.println(Thread.currentThread().getName() + " say Hello");
        // Calling another synchronized method inside a synchronized method
        seyHi();
    }
    // Define a synchronized method (a synchronized method called by another synchronized method)
    public synchronized void seyHi() {
        System.out.println(Thread.currentThread().getName() + " say Hi");
    }
}

public class ReentrantLockDemo {
    public static void main(String[] args) {
        ThreadDemo demo = new ThreadDemo();
        new Thread(() -> {
            demo.seyHello();
        },"T1").start();

        new Thread(() -> {
            demo.seyHello();
        },"T2").start();
    }
}

output:

spin lock

It means that the thread trying to acquire the lock will not block immediately, but will try to acquire the lock in a loop. The advantage of this is to reduce the consumption of thread context switching. The disadvantage is that the loop will consume CPU.

The benefits of spin locks

The loop compares the acquisition until it succeeds, there is no blocking like wait

Example

package concurrency.day0411;

import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicReference;

/**
 * @author Woo_home
 * @create by 2020/4/11  20:42
 */
public class CASDemo {

    // atomic reference thread
    AtomicReference<Thread> atomicReference = new AtomicReference<>();

    public void myLock() {
        Thread thread = Thread.currentThread();
        System.out.println(Thread.currentThread().getName() + "\t come in");
        while (!atomicReference.compareAndSet(null,thread)) {

        }
    }

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

    public static void main(String[] args) {
        CASDemo casDemo = new CASDemo();

        new Thread(() -> {
            casDemo.myLock();
            try {
                TimeUnit.SECONDS.sleep(5);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            casDemo.myUnLock();
        },"A").start();

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

        new Thread(() -> {
            casDemo.myLock();
            try {
                TimeUnit.SECONDS.sleep(1);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            casDemo.myUnLock();
        },"B").start();
    }
}

output:

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

  • Exclusive lock (write lock): means that the lock can only be held by one thread at a time. Exclusive locks for both ReentrantLock and synchronized
  • Shared lock (read lock): refers to the lock that can be held by multiple thread locks. For ReentrantReadWriteLock, its read lock is a shared lock, and its write lock is an exclusive lock. The shared lock of the read lock can ensure that concurrent reading is very efficient, and the processes of reading and writing, writing and reading, and writing and writing are mutually exclusive.

There is no problem with multiple threads reading a resource class at the same time, so in order to meet the concurrency, reading shared resources should be possible at the same time. But if one thread wants to write to a shared resource, no other thread should be able to read or write to that resource

Small summary:

  • read - read can coexist
  • read-write cannot coexist
  • write - write cannot coexist

Write operation: atomic + exclusive, the whole process must be a complete unity, the middle must not be divided or interrupted

Example (without using read-write lock)

package concurrency.day0411;

import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.TimeUnit;

/**
 * @author Woo_home
 * @create by 2020/4/12  12:39
 */

class MyCache { // resource class
    private volatile Map<String,Object> map = new HashMap<>();

	// write operation
    public void put(String key, Object value) {
        System.out.println(Thread.currentThread().getName() + "\t Writing:" + key);
        try {
            TimeUnit.MILLISECONDS.sleep(300);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        map.put(key,value);
        System.out.println(Thread.currentThread().getName() + "\t Write complete:" + key);
    }

	// read operation
    public void get(String key) {
        System.out.println(Thread.currentThread().getName() + "\t Reading:");
        try {
            TimeUnit.MILLISECONDS.sleep(300);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        Object result = map.get(key);
        System.out.println(Thread.currentThread().getName() + "\t Read complete:" + result);
    }
}

public class ReadWriteLockDemo {
    public static void main(String[] args) {
        MyCache myCache = new MyCache();

		// 5 threads write data
        for (int i = 0; i < 5; i++) {
            final int tempInt = i;
            new Thread(() -> {
                myCache.put(tempInt+"",tempInt+"");
            },String.valueOf(i)).start();
        }

		// 5 threads read data
        for (int i = 0; i < 5; i++) {
            final int tempInt = i;
            new Thread(() -> {
                myCache.get(tempInt+"");
            },String.valueOf(i)).start();
        }
    }
}

output:

It can be found that the printing is messy, and one thread has not yet finished writing and another thread has followed the writing.

Example (using read-write locks)

package concurrency.day0411;

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

/**
 * @author Woo_home
 * @create by 2020/4/12  12:39
 */

class MyCache { // resource class
    private volatile Map<String,Object> map = new HashMap<>();

	// Use read-write locks
    private ReentrantReadWriteLock readWriteLock = new ReentrantReadWriteLock();

    public void put(String key, Object value) {
    	// lock before write
        readWriteLock.writeLock().lock();
        try {
            System.out.println(Thread.currentThread().getName() + "\t Writing:" + key);
            try {
                TimeUnit.MILLISECONDS.sleep(300);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            map.put(key,value);
            System.out.println(Thread.currentThread().getName() + "\t Write complete:" + key);
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
        	// Unlock after writing
            readWriteLock.writeLock().unlock();
        }

    }

    public void get(String key) {
    	// lock before read
        readWriteLock.readLock().lock();
        try {
            System.out.println(Thread.currentThread().getName() + "\t Reading:");
            try {
                TimeUnit.MILLISECONDS.sleep(300);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            Object result = map.get(key);
            System.out.println(Thread.currentThread().getName() + "\t Read complete:" + result);
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
        	// Unlock after reading
            readWriteLock.readLock().unlock();
        }
    }
}

public class ReadWriteLockDemo {
    public static void main(String[] args) {
        MyCache myCache = new MyCache();

        for (int i = 0; i < 5; i++) {
            final int tempInt = i;
            new Thread(() -> {
                myCache.put(tempInt+"",tempInt+"");
            },String.valueOf(i)).start();
        }

        for (int i = 0; i < 5; i++) {
            final int tempInt = i;
            new Thread(() -> {
                myCache.get(tempInt+"");
            },String.valueOf(i)).start();
        }
    }
}

Posted by RobNewYork on Tue, 24 May 2022 02:55:52 +0300