共享模型之无锁

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也会撤销偏向锁,那么这时候如果有多线程访问该对象,但是没有发生竞争,则会偏向于第一个访问它的线程

轻量级锁

当一个对象被多个线程访问,但是这多个线程的访问时间是错开的,也就是它们并不是同时访问,没有竞争,那么这时候就是一个轻量级锁 的情况。

重量级锁

当一个对象被多个线程访问,且产生竞争,那么就会从轻量级锁升级为重量级锁

重量级锁竞争使用自旋来进行优化

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值