Implementation of synchronized and ReentrantLock locks

Implementation of synchronized lock

Synchronized is a keyword that modifies code blocks and methods to automatically acquire/release locks.

Implementation method: Each object of java can be regarded as a monitor lock. Whenever the thread code enters the synchronized code block, it will automatically acquire the lock. At this time, other threads will block until the current thread is executed. Or throwing an exception, wait, etc. will release the lock, which is based on entering and exiting the monitor to achieve method synchronization and code block synchronization.

A synchronous method uses ACC_SYNCHRONIZED to mark whether it is a synchronous method. When the method is called, the call instruction will check whether the ACC_SYNCHRONIZED access flag of the method is set. If it is set, the flag indicates that when the thread enters the method, it needs monitorenter, and when it exits the method, it needs monitorexit.

The synchronization of the code blocks utilizes the two bytecode instructions monitorenter and monitorexit.

When the virtual machine executes the monitorenter instruction, it first tries to acquire the lock of the object.

The current thread owns the lock of this object, and the counter of the lock is +1; when the monitorexit instruction is executed, the modulo counter is -1; when the counter is 0, the lock is released, and synchronized in Java is achieved by setting a mark in the object header. For the purpose of acquiring and releasing locks.

Implementation of ReentrantLock lock:

ReentrantLock is implemented based on the idea of ​​AQS (AbstractQueuedSynchronizer), so here is a brief description of the idea of ​​AQS.

AQS implementation principle

There is a state variable inside to indicate whether the lock is used or not, and it is initialized to 0. Under multi-threaded conditions, the thread must first obtain the state to execute the code in the critical section (synchronous code). After a thread obtains successfully, the state is increased by 1, and other threads If you get it again, because the shared resource has been occupied, it will go to the FIFO (first in first out) queue to wait. After the thread that occupies the state finishes executing the code in the critical section to release the resource (state minus 1), it will wake up

The next waiting thread in the FIFO (the next node in the head) gets state. Since state is a multi-thread shared variable, it must be defined as volatile to ensure the visibility of the state. At the same time, although volatile can guarantee visibility, it cannot guarantee atomicity. Therefore, AQS provides an atomic operation method for state to ensure thread safety. .

In addition, the FIFO queue implemented in AQS is actually implemented by a doubly linked list. The head node represents the currently occupied thread, and other nodes queue up to wait for the lock to be released because they cannot acquire the lock temporarily.

The queue is composed of Node objects, and Node is an internal class in AQS.

AbstractQueuedSynchronizer member

private transient volatile Node head;
private transient volatile Node tail;
/*
使用变量 state 表示锁状态,0-锁未被使用,大于 0 锁已被使用
共享变量 state,使用 volatile 修饰保证线程可见性
*/
private volatile int state;

State information is manipulated through getState , setState , compareAndSetState .

protected final int getState() { //获得锁状态
return state;
}
protected final void setState(int newState) {//设置锁状态
state = newState;
}
//使用 CAS 机制设置状态
protected final boolean compareAndSetState(int expect, int update) {
return unsafe.compareAndSwapInt(this, stateOffset, expect, update);
}

Some operation methods of AQS:

acquire: Indicates that the lock must be acquired

public final void acquire(int arg) {
if (!tryAcquire(arg) &&
acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
selfInterrupt();
}

tryAcquire: Try to acquire the lock, if tryAcquire acquires the lock successfully, then! tryAcquire(arg) is false, indicating that the lock has been acquired, and there is no need to participate in queuing at all, that is, there is no need to execute subsequent judgment conditions. Return directly according to the short-circuit rule of the judgment condition.

addWaiter: After trying to acquire the lock fails, encapsulate the current thread into a Node object, add it to the end of the queue, and return the Node node.

acquireQueued: After adding the thread to the queue, acquire the lock in a spin mode

release release lock

tryRelease: Release the lock and modify the state value to 0

unparkSuccessor: Unpark the node's successor (if present)

The lock mode of AQS is divided into: exclusive and shared

Exclusive lock: Only one thread can hold the lock at a time. For example, ReentrantLock is a mutex implemented exclusively.

Shared lock: Allows multiple threads to acquire locks at the same time and access shared resources concurrently, better than ReentrantReadWriteLock

The above is an implementation idea of ​​AQS

ReentrantLock is based on AQS. In concurrent programming, it can implement fair locks and unfair locks to synchronize shared resources. At the same time, like synchronized, ReentrantLock supports reentrancy. In addition, ReentrantLock is more flexible in scheduling and supports more Rich features.

ReentrantLock has a total of three inner classes, and the three inner classes are closely related.

There are a total of three classes Sync, NonfairSync, and FairSync inside the ReentrantLock class, and NonfairSync and

The FairSync class inherits from the Sync class, and the Sync class inherits from the AbstractQueuedSynchronizer abstract class.

ReentrantLock construction method:

The Sync class inherits AbstractQueuedSynchronizer

abstract static class Sync extends AbstractQueuedSynchronizer

The NonfairSync class inherits the Sync class, indicating that the lock is acquired using an unfair strategy, and it implements the abstract lock method in the Sync class

static final class NonfairSync extends Sync {
//加锁
final void lock() {
//若通过 CAS 设置变量 state 成功,就是获取锁成功,则将当前线程设置为独占线程。
//若通过 CAS 设置变量 state 失败,就是获取锁失败,则进入 acquire 方法进行后续处理。
if (compareAndSetState(0, 1))
setExclusiveOwnerThread(Thread.currentThread());
else
acquire(1);
}
//尝试获取锁,无论是否获得都立即返回
protected final boolean tryAcquire(int acquires) {
return nonfairTryAcquire(acquires);
}
}

The FairSync class also inherits the Sync class, which means that the fair strategy is used to acquire the lock, and it implements the abstract lock method in the Sync class.

static final class FairSync extends Sync {
final void lock() {
// 以独占模式获取对象,忽略中断
acquire(1);//底层实现交由 AbstractQueuedSynchronizer
}
}

Guess you like

Origin blog.csdn.net/weixin_71243923/article/details/128924111