CAS和volatile
首先看以下代码,这里主要观察withdraw方法,这段方法使用了AtomicInteger有关变量,在compareAndSet方法中,在set之前,先回拿prev这个值和旧值做比较,比如一开始prev拿到了100,当其他线程发生减法使主内存的值变为了90,那么在进行compareAndSet这个方法时,比较出旧的值(100)与当前值(90)不一致,那么next将作废,返回false,会重新执行while语句,直到一致为止,那么才会将
class AccountSafe implements Account { private AtomicInteger balance; public AccountSafe(Integer balance) { this.balance = new AtomicInteger(balance); } @Override public Integer getBalance() { return balance.get(); } @Override public void withdraw(Integer amount) { while (true) { int prev = balance.get(); int next = prev - amount; if (balance.compareAndSet(prev, next)) { break; } } // 可以简化为下面的方法 // balance.addAndGet(-1 * amount); } }
这里最关键的就是compareAndSet方法,即CAS原理,这个操作也是原子操作
CAS底层原理
CAS底层是lock comxchg指令,在单核、多核情况下能够保证比较-交换的原子性。
CAS属于UnSafe类,其底层的方法都是被native修饰,所以可以直接访问本地接口,即可以操作底层操作系统去响应任务
在多核情况下,某个核执行到lock的指令,CPU会让总线锁住,当这个核把指令执行完毕后,再开启总线。这个过程中不会进行上下文切换
@Slf4j public class SlowMotion { public static void main(String[] args) { AtomicInteger balance = new AtomicInteger(10000); int mainPrev = balance.get(); log.debug("try get {}", mainPrev); new Thread(() -> { sleep(1000); int prev = balance.get(); balance.compareAndSet(prev, 9000); log.debug(balance.toString()); }, "t1").start(); sleep(2000); log.debug("try set 8000..."); boolean isSuccess = balance.compareAndSet(mainPrev, 8000); log.debug("is success ? {}", isSuccess); if(!isSuccess){ mainPrev = balance.get(); log.debug("try set 8000..."); isSuccess = balance.compareAndSet(mainPrev, 8000); log.debug("is success ? {}", isSuccess); } } private static void sleep(int millis) { try { Thread.sleep(millis); } catch (InterruptedException e) { e.printStackTrace(); } } }
这段代码输出结果为
所以可以看出CAS在判断旧值和预期值时,这一个过程是原子操作。而底层中jvm会帮我们实现CAS汇编指令,这是一种完全依赖于硬件功能,通过它实现了原子操作。CAS本身是一种系统原语,原语是属于操作系统的范围内,是由若干个指令完成的,用于完成某个功能的过程,并且原语的执行必须是连续的,在执行过程中是不能被阻断的,也就是说CAS是一条原子指令,不会造成所谓的不一致问题
也就是说CAS执行的指令属于操作系统范围,是原子操作,而我们平时执行的指令属于应用层范围,是可以改变顺序的
CAS包括三个值,内存位置的值、预期原值、新值。如果内存中的值和预期原值匹配,那么就会将其原子变量更改为新值
CAS的ABA问题如何解决
CAS在JDK1.5之后使用了AtomicStampedReference类,其内部维护了一个时间戳,当AtomicStampedReference对象被修改后,那么其时间戳也会随之改变,所以对比获得该对象与内存中的该对象的时间戳最新值去比较,那么肯定判断出是不相等的
原子操作类
我们熟知的原子操作类比如AtomicInteger、AtoMicBoolean这些类底层都是靠CAS+volatile来实现线程安全,也就是说它们在进行加减之类的操作时,那么都需要靠CAS来实现,然后其操作后的结果对其他线程是可见的
Synchronized优化
对于一个对象,其对象头通常包含了类指针和mark word,而这个mark word指针。而mark word主要存储两个值,即哈希码和分代年龄,而当该对象加锁之后,那么就会对其内部信息更换为标记位、线程锁记录指针、重量级锁指针、线程id等内容
偏向锁
当一个对象只有被一个线程访问时,那么此时是无竞争,该对象的对象头回加入线程id,而第一次访问会通过CAS将线程id加入到该对象的对象头内,而后面该线程再次访问该对象时,就不需要通过CAS访问了,那么其访问速度必然大大提高
这里注意,偏向锁升级为轻量级锁是因为有多个线程访问时就会升级,而这一个过程需要STW
访问对象的hashCode也会撤销偏向锁,那么这时候如果有多线程访问该对象,但是没有发生竞争,则会偏向于第一个访问它的线程
轻量级锁
当一个对象被多个线程访问,但是这多个线程的访问时间是错开的,也就是它们并不是同时访问,没有竞争,那么这时候就是一个轻量级锁 的情况。
重量级锁
当一个对象被多个线程访问,且产生竞争,那么就会从轻量级锁升级为重量级锁
重量级锁竞争使用自旋来进行优化