AtomicInteger原理,CAS 机制,乐观锁,ABA问题

CAS(Compare And Swap)

CAS的原理
  • 利用了现代处理器都支持的CAS的指令,
  • 循环这个指令,直到成功为止

例如要执行 count++ ,先执行 count++ ,再通过处理器的CAS指令,比较一下此时输入的 count值跟内存里保存的值是不是相等的,相等就执行交换,把加1后的值赋值给内存里的 count

示例:
  • 演示基本类型的原子操作类
 

java

代码解读

复制代码

import java.util.concurrent.atomic.AtomicInteger; /** *类说明:演示基本类型的原子操作类 */ public class UseAtomicInt { static AtomicInteger ai = new AtomicInteger(10); public static void main(String[] args) { //先get再加1 System.out.println("getAndIncrement==="+ai.getAndIncrement()); //先加1再get System.out.println("incrementAndGet==="+ai.incrementAndGet()); //先加再get System.out.println("addAndGet==="+ai.addAndGet(24)); //先get再加 System.out.println("getAndAdd==="+ai.getAndAdd(24)); } }

运行结果:

 

ini

代码解读

复制代码

getAndIncrement===10 incrementAndGet===12 addAndGet===36 getAndAdd===36

源码
  • 从 AtomicInteger 静态代码块可以看到,在类初始化的时候拿到值的偏移量(获取偏移量后直接操作内存,从内存取值,不通过缓冲区)。在构造函数保存初始值,并且是volatile修饰
 

java

代码解读

复制代码

public class AtomicInteger extends Number implements java.io.Serializable { private static final long serialVersionUID = 6214790243416807050L; private static final sun.misc.Unsafe U = sun.misc.Unsafe.getUnsafe(); private static final long VALUE; static { try { VALUE = U.objectFieldOffset (AtomicInteger.class.getDeclaredField("value")); } catch (ReflectiveOperationException e) { throw new Error(e); } } private volatile int value; /** * Creates a new AtomicInteger with the given initial value. * * @param initialValue the initial value */ public AtomicInteger(int initialValue) { value = initialValue; }

  • 查看decrementAndGet源码
 

java

代码解读

复制代码

public final int decrementAndGet() { return U.getAndAddInt(this, VALUE, -1) - 1; }

  • U是sun.misc.Unsafe类里面的,找到Unsafe里面的getAndAddInt
 

java

代码解读

复制代码

public final int getAndAddInt(Object var1, long var2, int var4) { int var5; do { var5 = this.getIntVolatile(var1, var2); } while(!this.compareAndSwapInt(var1, var2, var5, var5 + var4)); return var5; }

  • var1就是atomic对象,var2就是偏移量,var4就是要做的减1。var5是通过对象起始地址偏移VALUE的距离,拿到9(示例代码设置的9),是从主内存拿的。
  • compareAndSwapInt就是 CAS 机制
  • 首先计算var5 + var4的值,然后存储一份,然后再通过var1, var2在内存中拿到atomic的值,对比拿到的和var5 + var4的值是否相等,如果相等就允许var5减一(var5与var5 + var4交换)然后返回,如果不等就一直循环判断。
  • 在单线程中没有意义反而变复杂了
  • 假如有AB两个线程,A线程在执行var5 + var4的操作之后时间片段用完了,切换到B线程,B线程已经走完了所有流程,值已经被修改为9。再切换到A线程的时候接着执行通过var1, var2在内存中拿到atomic的值,对比拿到的和var5 + var4的值是否相等就会发现不相等了,因为已经被修改了,然后重新执行do循环里面的操作,重新在内存取值,取到9,然后判断是相等的,也就跳出循环了。
  • 对比拿到的和var5 + var4的值是否相等和对var5赋值是有CPU保证操作是原子性的
  • 这也是乐观锁
  • 没有通过复杂的机制来阻塞线程,并且达到目的,比加锁的代码效率提高很多
  • 但是只适合少量并发中使用,高并发中会导致CPU占用率高。
  • 这种乐观锁(无锁)机制还有一个ABA问题。假如操作的是引用类型,在AB两个线程中,一开始对象是a,在循环中间把对象换成了b,然后又换成了a,对比结果还是a,但是其实中间做了很多其他事情,在并发中可能会导致严重问题
CAS的问题
ABA问题

这种乐观锁(无锁)机制还有一个ABA问题。假如操作的是引用类型,在AB两个线程中,一开始对象是a,在循环中间把对象换成了b,然后又换成了a,对比结果还是a,但是其实中间做了很多其他事情,在并发中可能会导致严重问题

解决:使用 AtomicStampedReference

 

java

代码解读

复制代码

import java.util.concurrent.atomic.AtomicStampedReference; /** *类说明:演示带版本戳的原子操作类 */ public class UseAtomicStampedReference { static AtomicStampedReference<String> asr = new AtomicStampedReference("BALA-BALA",0); public static void main(String[] args) throws InterruptedException { //拿到当前的版本号(旧) final int oldStamp = asr.getStamp(); final String oldReference = asr.getReference(); System.out.println(oldReference+"============"+oldStamp); Thread rightStampThread = new Thread(new Runnable() { @Override public void run() { System.out.println(Thread.currentThread().getName()+":当前变量值:" +oldReference + "-当前版本戳:" + oldStamp + "-" + asr.compareAndSet(oldReference, oldReference + "+Java", oldStamp, oldStamp + 1)); } }); Thread errorStampThread = new Thread(new Runnable() { @Override public void run() { String reference = asr.getReference(); System.out.println(Thread.currentThread().getName() +":当前变量值:" +reference + "-当前版本戳:" + asr.getStamp() + "-" + asr.compareAndSet(reference, reference + "+C", oldStamp, oldStamp + 1)); } }); rightStampThread.start(); rightStampThread.join(); errorStampThread.start(); errorStampThread.join(); System.out.println(asr.getReference()+"============"+asr.getStamp()); } }

打印结果:

 

ini

代码解读

复制代码

BALA-BALA============0 Thread-0:当前变量值:BALA-BALA-当前版本戳:0-true Thread-1:当前变量值:BALA-BALA+Java-当前版本戳:1-false BALA-BALA+Java============1

开销问题

compare发现值一直不对,又返回重新执行,需要很大的开销。

只能保证一个共享变量的原子操作
  • 解决:把多个变量放在类中,再使用 AtomicReference
 

java

代码解读

复制代码

import java.util.concurrent.atomic.AtomicReference; /** *类说明:演示引用类型的原子操作类 */ public class UseAtomicReference { static AtomicReference<UserInfo> atomicUserRef; public static void main(String[] args) { UserInfo user = new UserInfo("AAA", 15);//要修改的实体的实例 atomicUserRef = new AtomicReference(user); UserInfo updateUser = new UserInfo("BBB",17); atomicUserRef.compareAndSet(user,updateUser); System.out.println(atomicUserRef.get()); System.out.println(user); } //定义一个实体类 static class UserInfo { private volatile String name; private int age; public UserInfo(String name, int age) { this.name = name; this.age = age; } public String getName() { return name; } public int getAge() { return age; } @Override public String toString() { return "UserInfo{" + "name='" + name + '\'' + ", age=" + age + '}'; } } }

打印结果:

 

ini

代码解读

复制代码

UserInfo{name='BBB', age=17} UserInfo{name='AAA', age=15}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值