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(); } } }