我们来尝试了解一下Java并发的底层实现,首先是什么是CAS指令

CAS是由操作系统提供的,来自底层的一个指令,进行原子性的访问和更新,所谓CAS,就是利用系统指令尝试更新,如果更新时值不变,则可以更新成功,不然就重试或者返回失败

AtomicInteger的内部属性可以看出,依赖于Unsafe的一些底层能力

而且value往往被加上了volatile 关键字来保证并发

对于更加具体的更新方式,可以参考下面的getAndIncrement的内部实现

图片

不断尝试更新,进行内存地址的偏移

这就是简单的CAS在Java api中的具体实现

对于CAS的使用,可以假设一个场景,就是保证只有一个线程可以去修改一个分区

如何去实现锁操作呢?

就是利用CAS去包裹对象,进行compareAndSet

Atomic包提供了基本的原子类和引用操作,是一些无锁并发操作的首选

在Java 9之后,提供了Variable Handle API,源于JEP 193,提供有序的操作

private static final VarHandle HANDLE = MethodHandles.lookup().findStaticVarHandle

(AtomicBTreePartition.class, “lock”);

private void acquireLock(){

long t = Thread.currentThread().getId();

while (!HANDLE.compareAndSet(this, 0L, t)){

// 等待一会儿,数据库操作可能比较慢

}

}

直接利用句柄来进行CAS

CAS为了避免ABA问题,还提供了版本号,从避免

最后,在使用CAS的时候,建议最好设置一下自旋次数,避免长时间获取不到的问题

然后是AbstractQueuedSynchronizer AQS ,这是各种同步结构的基础

API如下

图片

如何使用AQS

对于原本的Semaphore来实现同步,道格李选择使用了AQS来建立同步模型

AQS的内部结构,可以划分为

一个volatile修饰的整数成员,提供了getState 方法 setState方法

一个先入先出队列,用于保存竞争的线程

基于CAS的基础操作方法

利用AQS来保证的同步结构,必须要实现两个基本类型的方法,acquire获取资源的独占权,release操作释放某个资源的独占

简单的ReentranLock中这两个api的实现如下

public void lock() {

sync.acquire(1);

}

public void unlock() {

sync.release(1);

}

而且AQS可以有着公平性和非公平性的实现

非公平的tryAcquire为例,需要检测队列中是否有其他等待者

final boolean nonfairTryAcquire(int acquires) {

final Thread current = Thread.currentThread();

int c = getState();// 获取当前AQS内部状态量

if (c == 0) { // 0表示无人占有,则直接用CAS修改状态位,

if (compareAndSetState(0, acquires)) {// 不检查排队情况,直接争抢

setExclusiveOwnerThread(current);  //并设置当前线程独占锁

return true;

}

} else if (current == getExclusiveOwnerThread()) { //即使状态不是0,也可能当前线程是锁持有者,因为这是再入锁

int nextc = c + acquires;

if (nextc < 0) // overflow

throw new Error(“Maximum lock count exceeded”);

setState(nextc);

return true;

}

return false;

}

如果tryAcquire失败,就意味着锁失败了,进入排队竞争阶段,利用FIFO队列,实现了线程间对锁竞争的部分

当前的线程被包装为了一个排他模式,通过addWaiter方法添加到队列,如果当前的节点前面是头节点,试图获取锁,一切顺利成为头节点,不然就是等待再次获取

https://blog.csdn.net/u013378306/article/details/106526664

发表评论

邮箱地址不会被公开。 必填项已用*标注