一些概念的东西
原子性就是指该操作是不可再分的。 不论是多核还是单核,具有原子性的量,同一时刻只能有一个线程来对它进行操作。简而言之,在整个操作过程中不会被线程调度器中断的操作,都可认为是原子性。比如 a = 1;
非原子性:也就是整个过程中会出现线程调度器中断操作的现象
类似 “a ++” 这样的操作不具有原子性,因为它可能要经过以下两个步骤:
(1)取出 a 的值
(2)计算 a+1
如果有两个线程 t1,t2 在进行这样的操作。t1 在第一步做完之后还没来得及加 1 操作就被线程调度器中断了,于是 t2 开始执行,t2 执行完毕后 t1 开始执行第二步 (此时 t1 中 a 的值可能还是旧值,不是一定的,只有线程 t2 中 a 的值没有及时更新到 t1 中才会出现)。这个时候就出现了错误,t2 的操作相当于被忽略了
JAVA 的线程调度—抢占式调度 (优先级)
CAS—(Compare And Swap) 比较并交换
采用乐观锁思想—总是认为自己可以完成操作
在多个线程操作一个变量时,只有一个线程会成功更新,其他失败,失败线程允许重试
CAS 自旋等待
用锁或 synchronized
关键字可以实现原子操作,那么为什么还要用 CAS 呢,因为加锁或使用 synchronized
关键字带来的性能损耗较大,而用 CAS 可以实现乐观锁,它实际上是直接利用了 CPU 层面的指令,所以性能很高。
那么 CAS 是怎么样实现自旋锁,CAS 利用 CPU 指令保证了操作的原子性,以达到锁的效果,至于自旋,一般是用一个无限循环实现。这样一来,一个无限循环中,执行一个 CAS 操作,当操作成功,返回 true 时,循环结束;当返回 false 时,接着执行循环,继续尝试 CAS 操作,直到返回 true。
进入 CAS 源码看
import java.util.concurrent.atomic.AtomicInteger;
以原子方式将值设置为给定的更新值 (update),如果当前值等于期望值 (expect)。 返回 ture,更新变量的值,实际值不等于期望值返回 false,线程什么都不做,CAS 返回当前变量值;
public final boolean compareAndSet(int expect, int update) {
return unsafe.compareAndSwapInt(this, valueOffset, expect, update);
}
进去unsafe.compareAndSwapInt(this, valueOffset, expect, update);
它是个 native 方法,用 c++ 实现,有兴趣的可以去看看 JDK 源代码中 safe.cpp 文件,所以 CAS 直接跟操作系统进行操作的;
public final native boolean compareAndSwapObject(Object var1, long var2, Object var4, Object var5);
public final native boolean compareAndSwapInt(Object var1, long var2, int var4, int var5);
public final native boolean compareAndSwapLong(Object var1, long var2, long var4, long var6);
CAS 产生的 ABA 问题
什么是 ABA?
上面我可以知道,CAS 算法实现有一个重要的前提:需要取出内存中某一时刻的数据,然后在下一时刻进行比较,替换,在这个时间差,数据可能已经发生变化,这就导致了 ABA 问题;
解决方法:部分是通过加版本号(version)
来解决,
AQS 是一个抽象的队列同步器
AQS
维护了一个volatile
语义 (支持多线程下的可见性) 的共享资源变量state
和一个FIFO
线程等待队列 (多线程竞争 state 被阻塞时会进入此队列)。
java.util.concurrent.locks.AbstractQueuedSynchronizer
抽象类,简称 AQS
AQS 共享资源的方式:独占式和共享式
AQS 默认提供了独占式和共享式两种模式,JDK 对应的实现有ReentrantLock
和ReentrantReadWriteLock
独占式:只有一个线程能执行,具体的 JAVA 实现有 ReentrantLock
共享式:多个线程同时执行