我们来尝试了解一下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方法添加到队列,如果当前的节点前面是头节点,试图获取锁,一切顺利成为头节点,不然就是等待再次获取