"Microservices in Practice" Chapter 26 Classification of Java Locks

Series Article Directory

Chapter 28 Distributed Lock Framework-Redisson
Chapter 27 CAS
Chapter 26 Classification of Java Locks
Chapter 25 Java Multithreading Security and Locks
Chapter 2 Application of CountDownLatch and Semaphone
Chapter 1 Application of Java Thread Pool Technology

insert image description here



foreword

This chapter introduces several common locks in Java: fair locks and unfair locks, reentrant locks, exclusive locks/shared locks, mutex locks/read-write locks, optimistic locks/pessimistic locks, segmented locks, biased locks/lightweight locks/heavyweight locks, and spin locks.

1. Fair lock and unfair lock

A fair lock means that multiple threads acquire locks in the order in which they apply for locks.
Unfair lock means that the order in which multiple threads acquire locks is not in the order in which they apply for locks. It is possible that threads that apply later acquire locks first than threads that apply first. Possibly, it will cause priority inversion or starvation.
For Java ReentrantLock, the constructor specifies whether the lock is a fair lock. The default is an unfair lock. The advantage is that the throughput is greater than that of a fair lock.
For synchronized, it is also an unfair lock. Since it does not implement thread scheduling through AQS like ReentrantLock, there is no way to make it a fair lock.

package com.xxxx.reids.thread;

import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;

/***
 * @title ReentrantLockFair
 * @desctption 公平锁
 * @author Kelvin
 * @create 2023/5/29 16:10
 **/
public class ReentrantLockFair {
    
    

    public static void main(String[] args) {
    
    
        //true表示公平锁
        Lock lock = new ReentrantLock(true);
        for (int i = 0; i < 3; i++) {
    
    
            new Thread(){
    
    
                @Override
                public void run() {
    
    
                    for (int j = 0; j < 2; j++) {
    
    
                        lock.lock();
                        System.out.println(Thread.currentThread().getName());
                        lock.unlock();
                    }
                }
            }.start();
        }
    }

}

```java
package com.xxxx.reids.thread;

import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;

/***
 * @title ReentrantLockFair
 * @desctption 非公平锁
 * @author Kelvin
 * @create 2023/5/29 16:10
 **/
public class ReentrantLockFair {
    
    

    public static void main(String[] args) {
    
    
        //false表示非公平锁
        Lock lock = new ReentrantLock(false);
        for (int i = 0; i < 3; i++) {
    
    
            new Thread(){
    
    
                @Override
                public void run() {
    
    
                    for (int j = 0; j < 2; j++) {
    
    
                        lock.lock();
                        System.out.println(Thread.currentThread().getName());
                        lock.unlock();
                    }
                }
            }.start();
        }
    }

}

2. Reentrant lock

Reentrant lock, also known as recursive lock, means that when the same thread acquires the lock in the outer method, it will automatically acquire the lock when entering the inner method (that is, it can re-enter to obtain the lock). For Java ReentrantLock, its name is Reentrant Lock, which is the re-entrant lock. For synchronized, it is also a reentrant lock. One advantage of reentrant locks is that deadlocks can be avoided to a certain extent
(reentrant locks refer to threads as units. When a thread acquires an object lock, the thread can acquire the lock on the object again, while other threads cannot.)

synchronized void setA() throws Exception{
Thread.sleep(1000);
setB();
}
synchronized void setB() throws Exception{
Thread.sleep(1000);
}

/***
 * @title ReentrantLockTest
 * @desctption 可重入锁测试
 * @author Kelvin
 * @create 2023/5/29 16:29
 **/
public class ReentrantLockTest {
    
    

    public static void main(String[] args) {
    
    
        Object obj = new Object();
        new Thread(() -> {
    
    
            //第一次加锁
            synchronized (obj) {
    
    
                System.out.println(Thread.currentThread().getName() + "第一层");
                //第二次加锁,此时obj对象处于锁定状态,但是当前线程仍然可以进入,避免死锁
                synchronized (obj) {
    
    
                    int i = 1 / 0;
                    System.out.println(Thread.currentThread().getName() + "第二层");
                }
            }
        }, "t1").start();
        new Thread(() -> {
    
    
            //第一次加锁
            synchronized (obj) {
    
    
                System.out.println(Thread.currentThread().getName() + "第一层");
                //第二次加锁,此时obj对象处于锁定状态,但是当前线程仍然可以进入,避免死锁
                synchronized (obj) {
    
    
                    System.out.println(Thread.currentThread().getName() + "第二层");
                }
            }
        }, "t2").start();
    }

}


Exercise: Testing reentrant locks with ReentrantLock

3. Exclusive lock/shared lock

An exclusive lock means that the lock can only be held by one thread at a time; a shared lock means that the lock can be held by multiple threads

For Java ReentrantLock, it is an exclusive lock. But for another implementation class of Lock, ReadWriteLock, 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, reading and writing, and writing and writing are mutually exclusive. Exclusive locks and shared locks are also implemented through AQS, by implementing different methods to achieve exclusive or shared. For synchronized, of course it is an exclusive lock.
(The read lock uses the shared mode; the write lock uses the exclusive mode; the read lock can be held by multiple threads at the same time when there is no write lock, and the write lock is exclusive. When there is a read lock, the write lock cannot be obtained; and when there is a write lock, except for the thread that obtained the write lock can obtain the read lock, other threads cannot obtain the read lock)

import java.util.concurrent.BrokenBarrierException;
import java.util.concurrent.CyclicBarrier;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.locks.ReentrantReadWriteLock;

/***
 * @title WriteAndReedLockTest
 * @desctption 读写锁
 * @author kelvin
 * @create 2023/5/29 16:58
 **/
public class WriteAndReedLockTest {
    
    
    private static ReentrantReadWriteLock reentrantReadWriteLock= new ReentrantReadWriteLock();
    private static ExecutorService executorService = Executors.newFixedThreadPool(3);
    //执行三个线程进行读写操作,并设置一个屏障,线程依次准备就绪后未获取锁之前都在等待,当第三个线程执行 cyclicBarrier.await();后屏障解除,三个线程同时执行。
    private static CyclicBarrier cyclicBarrier = new CyclicBarrier(3);
    private static Integer i = 100;
    public static void main(String[] args) {
    
    
        executorService.execute(
                () ->{
    
    
                    read();
                }
        );
        executorService.execute(
                () ->{
    
    
                    write();
                }
        );
        executorService.execute(
                () ->{
    
    
                    read();
                }
        );
    }

    private static void read(){
    
    
        try {
    
    
            cyclicBarrier.await();
        } catch (InterruptedException e) {
    
    
            e.printStackTrace();
        } catch (BrokenBarrierException e) {
    
    
            e.printStackTrace();
        }
        //获取读锁
        reentrantReadWriteLock.readLock().lock();
        System.out.println("Read," + Thread.currentThread().getName() + ",i = " + i);
        reentrantReadWriteLock.readLock().unlock();
    }

    private static void write(){
    
    
        try {
    
    
            cyclicBarrier.await();
        } catch (InterruptedException e) {
    
    
            e.printStackTrace();
        } catch (BrokenBarrierException e) {
    
    
            e.printStackTrace();
        }
        //获取写锁
        reentrantReadWriteLock.writeLock().lock();
        i ++;
        System.out.println("Write," + Thread.currentThread().getName() + ",i = " + i);
        reentrantReadWriteLock.writeLock().unlock();
    }
}

4. Mutex / read-write lock

Exclusive lock/shared lock is a broad term, and mutex lock/read-write lock is a specific implementation. The specific implementation of mutex in Java is ReentrantLock; the specific implementation of read-write lock in Java is Read/WriteLock.

5. Optimistic lock/pessimistic lock

Optimistic locks and pessimistic locks do not refer to specific types of locks, but refer to the perspective of concurrent synchronization.

  • Pessimistic locking: always assume the worst case, think that others will modify it every time you get the data, so every time you get the data, you will lock it, so that others will block until they get the lock if they want to get the data. Many such locking mechanisms are used in traditional relational databases, such as row locks, table locks, etc., read locks, write locks, etc., all of which are locked before operations are performed. Another example is the implementation of the synchronized keyword in Java, which is also a pessimistic lock.
  • Optimistic lock: As the name suggests, it is very optimistic. Every time you go to get the data, you think that others will not modify it, so it will not be locked. However, when updating, you will judge whether others have updated the data during this period. You can use mechanisms such as version numbers. Optimistic locking is suitable for multi-read application types, which can improve throughput. Like the write_condition mechanism provided by the database, it is actually an optimistic locking provided. The atomic variable class under the java.util.concurrent.atomic package in Java is implemented using CAS, an implementation method of optimistic locking.
  • The atomic variable class under the java.util.concurrent.atomic package in Java is implemented using CAS (Compare and Swap), an implementation of optimistic locking.
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.TimeUnit;

/***
 * @title AtomicExample
 * @desctption 乐观锁/悲观锁
 * @author Kelvin
 * @create 2023/5/29 17:08
 **/
public class AtomicExample {
    
    
    private static Integer m = 1;
    public static void main(String[] args) throws InterruptedException {
    
    
        ExecutorService executorService = Executors.newFixedThreadPool(10);

        for (int i = 0; i < 1000; i++) {
    
    
            executorService.submit(
                    () -> {
    
    
                        m ++;
                    }
            );
        }
        TimeUnit.SECONDS.sleep(2);
        System.out.println(m);
    }
}
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicInteger;

/***
 * @title AtomicExample
 * @desctption 乐观锁/悲观锁
 * @author Kelvin
 * @create 2023/5/29 17:08
 **/
public class AtomicExample {
    
    
    private static AtomicInteger m = new AtomicInteger(1);
    public static void main(String[] args) throws InterruptedException {
    
    
        ExecutorService executorService = Executors.newFixedThreadPool(10);

        for (int i = 0; i < 1000; i++) {
    
    
            executorService.submit(
                    () -> {
    
    
                        m.incrementAndGet();
                    }
            );
        }
        TimeUnit.SECONDS.sleep(2);
        System.out.println(m.get());
    }
}

6. Segment lock

Segment lock is actually a lock design, not a specific lock. For ConcurrentHashMap, its concurrent implementation is to achieve efficient concurrent operations in the form of segment locks. The segment lock in ConcurrentHashMap is called Segment, which is similar to the structure of HashMap (the implementation of HashMap in JDK7 and JDK8), that is, it has an Entry array inside, and each element in the array is a linked list; ment inherits ReentrantLock). When you need to put elements, instead of locking the entire HashMap, you first know which segment it will be placed in through the hashcode, and then lock the segment, so when multi-threaded put, as long as it is not placed in a segment, the real parallel insertion is realized. However, when counting the size, it is necessary to obtain all the segment locks before obtaining the global information of HashMap.

The design purpose of the segmented lock is to refine the granularity of the lock. When the operation does not need to update the entire array, only one item in the array is locked.

7. Biased lock/lightweight lock/heavyweight lock

These three locks refer to the state of the lock and are for synchronized. In Java 5, efficient synchronization is achieved by introducing a lock escalation mechanism. The status of these three locks is indicated by the fields in the object header of the object monitor.

  • A biased lock means that a piece of synchronization code has been accessed by a thread, then the thread will automatically acquire the lock. Reduce the cost of acquiring locks.
  • Lightweight lock means that when the lock is a biased lock and is accessed by another thread, the biased lock will be upgraded to a lightweight lock, and other threads will try to acquire the lock in the form of spin, which will not block and improve performance.
  • A heavyweight lock means that when the lock is a lightweight lock, although another thread is spinning, the spin will not continue forever. When the lock is not acquired after spinning for a certain number of times, it will enter blocking, and the lock will expand into a heavyweight lock. Heavyweight locks will block other applied threads and reduce performance.

8. Spin lock

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

Guess you like

Origin blog.csdn.net/s445320/article/details/130930596