synchronized和ReentrantLock锁的实现

synchronized锁的实现

synchronized是一个关键字,修饰代码块和方法,自动获取锁/释放锁.

实现方式: java的每个对象都可以把它当做一个监视器锁,每当线程代码进入到synchronized代码块时候,都会自动获取锁,这时其他线程来访问就会阻塞,直到当前线程执行完,或者抛出异常,wait等都会释放锁,它都是基于进入和退出监视器来实现方法同步和代码块同步.

同步方法使用 ACC_SYNCHRONIZED 标记是否为同步方法。当方法调用时,调用指令将会检查方法的 ACC_SYNCHRONIZED 访问标志是否被设置,如果设置了,该标记表明线程进入该方法时,需要monitorenter,退出该方法时需要 monitorexit.

代码块的同步是利用 monitorenter 和 monitorexit 这两个字节码指令。

在虚拟机执行到 monitorenter 指令时,首先要尝试获取对象的锁。

当前线程拥有了这个对象的锁,把锁的计数器+1;当执行 monitorexit 指令时将模计数器-1;当计数器为 0 时,锁就被释放了,Java 中 synchronized 通过在对象头设置标记,达到了获取锁和释放锁的目的.

ReentrantLock锁的实现:

ReentrantLock是基于AQS(AbstractQueuedSynchronizer)的思想来实现的,所以在这里简单描述一下AQS思想.

AQS实现原理

在内部有一个 state 变量表示锁是否使用, 初始化 0,在多线程条件下,线程要执行临界区(同步代码)的代码,必须首先获取 state,某个线程获取成功之后, state加 1,其他线程再获取的话由于共享资源已被占用,所以会到 FIFO(先进先出) 队列去等待,等占有 state 的线程执行完临界区的代码释放资源( state 减 1)后,会唤醒

FIFO 中的下一个等待线程(head 中的下一个结点)去获取 state。state 由于是多线程共享变量,所以必须定义成 volatile,以保证 state 的可见性, 同时虽然 volatile 能保证可见性,但不能保证原子性,所以 AQS 提供了对 state 的原子操作方法,保证了线程安全。

另外 AQS 中实现的 FIFO 队列其实是双向链表实现的,head 结点代表当前占用的线程,其他节点由于暂时获取不到锁所以依次排队等待锁释放。

队列由 Node 对象组成,Node 是 AQS 中的内部类。

AbstractQueuedSynchronizer 成员

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

状态信息通过 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);
}

AQS的一些操作方法:

acquire: 表示一定能获取锁

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

tryAcquire: 尝试获取锁,如果 tryAcquire 获取锁成功,那么!tryAcquire(arg)为 false,说明已经获取锁,根本不用参与排队,也就是不用再执行后续判断条件。根据判断条件的短路规则,直接返回。

addWaiter: 尝试获取锁失败后,将当前线程封装到一个 Node 对象中,添加到队尾,并返回 Node 节点.

acquireQueued: 将线程添加到队列后,以自旋的方式去获取锁

release 释放锁

tryRelease: 释放锁,将 state 值进行修改为 0

unparkSuccessor: 唤醒节点的后继者(如果存在)

AQS 的锁模式分为:独占和共享

独占锁:每次只能有一个线程持有锁,比如 ReentrantLock 就是以独占方式实现的互斥锁。

共 享 锁 : 允 许 多 个 线 程 同 时 获 取 锁 , 并 发 访 问 共 享 资 源 , 比 ReentrantReadWriteLock

以上就是AQS的一个实现思想

ReentrantLock 基于 AQS,在并发编程中它可以实现公平锁和非公平锁来对共享资源进行同步,同时和 synchronized 一样,ReentrantLock 支持可重入,除此之外,ReentrantLock 在调度上更灵活,支持更多丰富的功能。

ReentrantLock 总共有三个内部类,并且三个内部类是紧密相关的.

ReentrantLock 类内部总共存在 Sync、NonfairSync、FairSync 三个类,NonfairSync 与

FairSync 类 继 承 自 Sync 类 , Sync 类 继 承自 AbstractQueuedSynchronizer 抽象类。

ReentrantLock构造方法:

Sync 类继承 AbstractQueuedSynchronizer

abstract static class Sync extends AbstractQueuedSynchronizer

NonfairSync 类继承了 Sync 类,表示采用非公平策略获取锁,其实现了 Sync 类中抽象的 lock 方法

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

FairSync 类也继承了 Sync 类,表示采用公平策略获取锁,其实现了 Sync 类中的抽象 lock 方法.

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

猜你喜欢

转载自blog.csdn.net/weixin_71243923/article/details/128924111