Java Concurrent Programming (4) Thread Synchronization [AQS|Lock]

Overview

In Java, locking can be used to ensure resource access security when multiple threads access a certain public resource. Java proposes two ways to lock

  • The first one is locking through the keyword synchronized as we mentioned above. The underlying layer of synchronized is hosted by the JVM for execution, and a lot of optimizations have been made after Java 1.6 (bias lock, spin, lightweight lock), which is very convenient to use. And the performance is also very good, so it is recommended to use synchronized for synchronization operations when it is not necessary;
  • The second is to lock through the Lock under the java.util.concurrent package that will be introduced in this article ( lock uses CAS+spin extensively. Therefore, according to the characteristics of CAS, it is recommended to use lock in the case of low lock conflicts )

AQS

Overview

  • The full name of AQS is AbstractQueuedSynchronizer, which is translated as abstract queue synchronizer.
  • The underlying data structure of AQS is volatile modified state and a Node bidirectional queue.
  • The implementation classes under Lock include ReentrantLock, ReadLock, and WriteLock, all of which are based on AQS to acquire or release lock resources.

internal structure

According to the source code, we can know that AQS maintains a volatile state and a CLH (FIFO) two-way queue.

state is an int type mutex variable modified by volatile. state= 0 means that no task thread is using the resource, and state>= 1 means that a thread is already holding the lock resource. The CLH queue is a FIFO queue maintained by the internal class Node.

Implementation principle

When a thread acquires a lock resource, it first determines whether the state is equal to 0 (lock-free state). If it is 0, the state is updated to 1. At this time, the lock resource is occupied. In this process, if multiple threads perform state update operations at the same time, it will cause thread safety issues. Therefore, the bottom layer of AQS adopts the CAS mechanism to ensure the atomicity of mutually exclusive variable state updates . The thread that has not obtained the lock is blocked through the park method in the Unsafe class. The blocked thread is placed in the CLH doubly linked list according to the first-in, first-out principle. When the thread that has obtained the lock releases the lock, it will start from the head of this doubly linked list. Wake up the next waiting thread and compete for the lock.

Fair lock and unfair lock

When competing for lock resources, fair locks need to determine whether there are blocked threads in the doubly linked list. If there are, they need to queue up and wait. The way unfair locks are handled is that regardless of whether there are blocked threads waiting in the queue in the doubly linked list, it will try to modify the state variable to compete for the lock, which is unfair to the threads queued in the linked list.

Lock interface

Lock implementation class

  • In JDK8, except StampedLock, which is a non-reentrant lock, other keywords including ReentrantLock, ReentrantReadWriteLock and Synchronized are all reentrant locks.
  • A reentrant lock means that a thread has preempted a mutex lock resource and can repeatedly acquire the lock resource before the lock is released. It only needs to record the number of reentrants and the state is incremented by 1.
  • Lock actually controls the lock holding situation by updating the state in AQS.

Lock method

// 尝试获取锁,获取成功则返回,否则阻塞当前线程
void lock();
// 尝试获取锁,线程在成功获取锁之前被中断,则放弃获取锁,抛出异常
void lockInterruptibly() throws InterruptedException;
// 尝试获取锁,获取锁成功则返回true,否则返回false
boolean tryLock();
// 尝试获取锁,若在规定时间内获取到锁,则返回true,否则返回false,未获取锁之前被中断,则抛出异常
boolean tryLock(long time, TimeUnit unit) throws InterruptedException;
// 释放锁
void unlock();
// 返回当前锁的条件变量,通过条件变量可以实现类似notify和wait的功能,一个锁可以有多个条件变量
Condition newCondition();

ReentrantLock

  • According to the source code, you can see that the key member variable Sync type that implements the lock function inherits AQS.
  • Sync has two implementation classes in ReentrantLock: NonfairSync fair lock type and FairSync unfair lock type
  • ReentrantLock is an unfair lock implementation by default. You can specify a fair lock or an unfair lock when instantiating it.

ReentrantLock acquisition lock process

//.lock()调用的是AQS的acquire()
public void lock() {
    sync.acquire(1);
}

public final void acquire(int arg) {
    //tryAcquire:会尝试通过CAS获取一次锁。
    //addWaiter:将当前线程加入双向链表(等待队列)中
    //acquireQueued:通过自旋,判断当前队列节点是否可以获取锁
    if (!tryAcquire(arg) &&
        acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
        selfInterrupt();
}

//---------------------非公平锁尝试获取锁的过程---------------------
protected final boolean tryAcquire(int acquires) {
	// AQS的nonfairTryAcquire()方法
    return nonfairTryAcquire(acquires);
}

final boolean nonfairTryAcquire(int acquires) {
	// 获取当前线程
    final Thread current = Thread.currentThread();
    // 获取state
    int c = getState();
    if (c == 0) {
    	// 目前没有线程获取锁,通过CAS(乐观锁)去修改state的值
        if (compareAndSetState(0, acquires)) {
        	// 设置持有锁的线程为当前线程
            setExclusiveOwnerThread(current);
            return true;
        }
    }
    // 锁的持有者是当前线程(重入锁)
    else if (current == getExclusiveOwnerThread()) {
    	// state + 1
        int nextc = c + acquires;
        if (nextc < 0) // overflow
            throw new Error("Maximum lock count exceeded");
        setState(nextc);
        return true;
    }
    return false;
}

//---------------------当前线程加入双向链表的过程---------------------
private Node addWaiter(Node mode) {
    Node node = new Node(mode);

    for (;;) {
    	// 获取末位节点
        Node oldTail = tail;
        if (oldTail != null) {
        	// 当前节点的prev设置为原末位节点
            node.setPrevRelaxed(oldTail);
            // CAS确保在线程安全的情况下,将当前线程加入到链表的尾部
            if (compareAndSetTail(oldTail, node)) {
            	// 原末位节点的next设置为当前节点
                oldTail.next = node;
                return node;
            }
        } else {
        	// 链表为空则初始化
            initializeSyncQueue();
        }
    }
}

//---------------------首节点自旋过程---------------------
final boolean acquireQueued(final Node node, int arg) {
    boolean interrupted = false;
    try {
        for (;;) {
            final Node p = node.predecessor();
            // 首节点线程去尝试竞争锁
            if (p == head && tryAcquire(arg)) {
            	// 成功获取到锁,从首节点移出(FIFO)
                setHead(node);
                p.next = null; // help GC
                return interrupted;
            }
            if (shouldParkAfterFailedAcquire(p, node))
                interrupted |= parkAndCheckInterrupt();
        }
    } catch (Throwable t) {
        cancelAcquire(node);
        if (interrupted)
            selfInterrupt();
        throw t;
    }
}

ReentrantLock release lock process

The essence of releasing the lock is to gradually decrement the state value State in AQS.

//.unlock()调用AQS的release()方法释放锁资源
public void unlock() {
    sync.release(1);
}

public final boolean release(int arg) {
	// Sync的tryRelease()方法
    if (tryRelease(arg)) {
        Node h = head;
        if (h != null && h.waitStatus != 0)
            unparkSuccessor(h);
        return true;
    }
    return false;
}

protected final boolean tryRelease(int releases) {
	// 获取状态
    int c = getState() - releases;
    if (Thread.currentThread() != getExclusiveOwnerThread())
        throw new IllegalMonitorStateException();
    boolean free = false;
    // 修改锁的持有者为null
    if (c == 0) {
        free = true;
        setExclusiveOwnerThread(null);
    }
    setState(c);
    return free;
}

ReentrantReadWriteLock

  • ReentrantReadWriteLock read-write lock can acquire read lock or write lock respectively, that is, separate the read and write operations of data ;
  • writeLock(): Obtain write lock
    • writeLock().lock(): lock the write lock
    • writeLock().unlock(): Release the lock for the write lock
  • readLock(): Obtain read lock
    • readLock().lock(): lock the read lock
    • readLock().unlock(): Release the lock for the read lock
  • Read locks use shared mode, and write locks use exclusive mode . That is, when there is no write lock, the read lock can be held by multiple threads at the same time; when there is a write lock, except for the thread that obtained the write lock, other threads cannot obtain the read lock; and when there is a read lock, Write lock cannot be obtained
  • Suitable for reading more and writing less application scenarios, such as caching

Condition

Overview 

  • Condition is also a thread communication mechanism that implements thread blocking and wake-up through await and singalAll()
  • The underlying data structure is a queue that reuses the Node class of AQS and is implemented by a linked list without a leading node.
  • Await implementation principle: Put the current thread into the Waiting blocking state through LockSupport.park, until other threads call signal or signalAll to move the head node of the waiting queue into the synchronization queue, giving it the opportunity to obtain the lock through spin
  • signal/signalAll: Move the head node of the waiting queue into the synchronization queue and wake up the thread through LockSupport.unpark
  • Comparison with Object’s wait/notify mechanism
    • Condition supports not responding to interrupts, but object cannot
    • Lock can support multiple condition waiting queues, and object can only support one.
    • Condition can set a timeout for await, but object cannot
  • The producer-consumer problem can be realized through Lock+Condition (there will be relevant examples in the concurrency practice chapter later)

Condition practice

package com.bierce;
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
/**
 * Lock + Condition: 实现线程按序打印
 * 案例:开启3个线程,id分别为A、B、C,并打印10次,而且按顺序交替打印如:ABCABCABC...
 */
public class TestCondition {
    public static void main(String[] args) {
        PrintByOrderDemo print = new PrintByOrderDemo();
        new Thread(() -> {
            for (int i = 1; i <= 10; i++) {// 打印10次
                print.loopA(i);
            }
        },"A").start();
        new Thread(() -> {
            for (int i = 1; i <= 10; i++) {
                print.loopB(i);
            }
        },"B").start();
        new Thread(() -> {
            for (int i = 1; i <= 10; i++) {
                print.loopC(i);
            }
        },"C").start();
    }
}
class PrintByOrderDemo{
    private int number = 1; //当前正在执行线程的标记
    private Lock lock = new ReentrantLock();
    private Condition ConditionA = lock.newCondition();
    private Condition ConditionB = lock.newCondition();
    private Condition ConditionC = lock.newCondition();

    public void loopA(int totalLoop){
        lock.lock();
        try {
            if ( number != 1){ //判断当前是否打印A
                ConditionA.await();
            }
            for (int i = 1; i <= 1; i++) {
                System.out.print(Thread.currentThread().getName()); //打印A
            }
            //唤醒其他线程
            number = 2;
            ConditionB.signal();
        }catch (Exception e){
            e.printStackTrace();
        } finally {
            lock.unlock();
        }
    }
    public void loopB(int totalLoop){
        lock.lock();
        try {
            if ( number != 2){ //判断当前是否打印B
                ConditionB.await();
            }
            for (int i = 1; i <= 1; i++) {
                System.out.print(Thread.currentThread().getName()); //打印B
            }
            //唤醒其他线程
            number = 3;
            ConditionC.signal();
        }catch (Exception e){
            e.printStackTrace();
        } finally {
            lock.unlock();
        }
    }
    public void loopC(int totalLoop){
        lock.lock();
        try {
            if ( number != 3){ //判断当前是否打印C
                ConditionC.await();
            }
            for (int i = 1; i <= 1; i++) {
                System.out.print(Thread.currentThread().getName()); //打印C
            }
            //唤醒其他线程
            number = 1;
            ConditionA.signal();
        }catch (Exception e){
            e.printStackTrace();
        } finally {
            lock.unlock();
        }
    }
}

Guess you like

Origin blog.csdn.net/qq_34020761/article/details/132234785