java 原子类操作_Java 原子操作类

CAS(Compare And Swap),比较并更新,指令级别保证这是一个原子操作,三个运算符: 一个内存地址V,一个期望值A,一个新值B;基本思路:如果地址V上的值和期望值A相等,就把新值B更新到内存,如果不是,循环(死循环,自旋)里不断的进行CAS操作;

如果需要获取原子操作类的值并更新,期望值与内存地址中的值不等,则循环(死循环,自旋)里不断的进行CAS操作;(Atomic类的实现)

如AtomicInteger的getAndIncrement()方法

public final int getAndIncrement() {

return unsafe.getAndAddInt(this, valueOffset, 1);

}

Unsafe的getAndAddInt方法,这种就是CAS的实现方式;

public final int getAndAddInt(Object var1, long var2, int var4) {

int var5;

//如果var1和var5值不等,会重新进入循环

do {

var5 = this.getIntVolatile(var1, var2);

} while(!this.compareAndSwapInt(var1, var2, var5, var5 + var4));

return var5;

}

var1:AtomicInteger对象本身

var2:该对象值得引用地址

var4:需要变动的值

var5:用var1和var2找到的内存中的真实值,是从主内存中拷贝到工作内存中的值

用该对象当前的值var1与var5比较,如果相等则更新新值(var5 + var4)到内存,不相等就会重新进入循环;

一般使用CAS是配合自旋一起使用的,通俗点说就是死循环里判断需要读写的内存位置的值(V)和预期值(A)是否相等,不相等就会重新进入循环,如果是高并发的场景,这会出现有很多请求多次循环也成功不了的情况,这给CPU带来非常大的消耗;

在Atomic包里一共提供了13个类,属于4种类型的更新方式,分别是原子更新基本类型,原子更新数组,原子更新引用和原子更新属性(字段);Atomic包里基本都是使用Unsafe实现的包装类;

原子更新基本类型类

AtomicBoolean:原子更新布尔类型

AtomicInteger:原子更新整型

AtomicLong:原子更新长整型

AtomicInteger常用方法:

intaddAndGet(intdelta): 以原子方式将输入的数值与实例中的值(AtomicInteger里的value)相加,并返回结果

booleancompareAndSet(intexpect,intupdate): 如果输入的数值等于预期值,则以原子方式将该值设置为输入的值

intgetAndIncrement(): 以原子方式将当前值加1,注意,这里返回的是自增前的值

voidlazySet(intnewValue): 最终会设置成newValue,使用lazySet设置值后,可能导致其他线程在之后的一小段时间内还可以读取到旧的值

intgetAndSet(intnewValue): 以原子方式设置为newValue的值,并返回旧值

Atomic包提供了三种基本类型的原子更新;Atomic包里的类基本都是使用Unsafe实现的

/**Unsafe.java

* 每次在执行CAS操作时,线程会根据valueOffset去内存中获取当前值去跟expect的值做对比如果一致则修改并返回true,如果不一致说明有别的线程也在修改此对象的值,则返回false

* value 表示 需要操作的对象

* valueOffset 表示 对象(value)的地址的偏移量(通过Unsafe.objectFieldOffset(Field valueField)获取)

* expect 表示更新时value的期待值

* update 表示将要更新的值

* @return 如果更新成功返回true,否则为false

*/

public final native boolean compareAndSwapObject(Object value, long valueOffset, Object expect, Object update);

public final native boolean compareAndSwapInt(Object value, long valueOffset, int expect, int update);

public final native boolean compareAndSwapLong(Object value, long valueOffset, long expect, long update);

ABA问题

《Java并发编程实战》的15.4.4节如下:

ABA问题是一种异常现象:如果在算法中的节点可以被循环使用,那么在使用“比较并交换”指令时就可能出现这个问题(如果在没有垃圾回收机制的环境 中)。在CAS操作中将判断“V的值是否仍然为A?”,并且如果是的话就继续执行更新操作。在大多数情况下,这种判断是足够的。然而,有时候还需要知道 “自从上次看到V的值为A以来,这个值是否发生了变化?”在某些算法中,如果V值首先由A变成B,再由B变成A,那么仍然被认为发生了变化,并需要重新执 行算法中的某些步骤。

比如说线程1从内存位置valueOffset中取出值A,线程2也从内存位置valueOffset中取出值A,并执行一些操作将值变为B,然后再将B变成A,这时候线程1进行CAS操作发现内存中仍然是A,线程1执行成功,但不能代表整个过程中,值没有被修改过;

public class UseAtomicIntegerTest {

static AtomicInteger integer = new AtomicInteger(1);

public static void main(String[] args) throws InterruptedException {

int oldValue = integer.get();

System.out.println(Thread.currentThread().getName() + " oldValue:" + oldValue);

Thread thread1 = new Thread(new Runnable() {

@Override

public void run() {

try {

System.out.println(Thread.currentThread().getName() + " --- " + integer.get());

Thread.sleep(2L);

} catch (InterruptedException e) {

e.printStackTrace();

}

}

});

Thread thread2 = new Thread(new Runnable() {

@Override

public void run() {

// 确保别的线程1先执行

Thread.yield();

// integer + 1

System.out.println(Thread.currentThread().getName() + " --- integer:" + integer.incrementAndGet());

// integer - 1

System.out.println(Thread.currentThread().getName() + " --- integer:" + integer.decrementAndGet());

}

});

thread1.start();

thread2.start();

thread1.join();

thread2.join();

boolean result = integer.compareAndSet(oldValue, oldValue + 10);

System.out.println(Thread.currentThread().getName() + " --- " + integer.get() + " result:" + result);

}

}

打印如下:

main oldValue:1

Thread-0 --- 1

Thread-1 --- integer:2

Thread-1 --- integer:1

main --- 11 result:true

执行最后的值是相同的,但不能代表整个过程中,值没有被修改过;

ABA解决方案:

给变量加一个版本号即可,在比较的时候不仅要比较当前变量的值,还需要比较当前变量的版本号;在Java5中,已经提供了AtomicStampedReference来解决问题,检查当前引用是否等于预期引用,其次检查当前标志是否等于预期标志,如果都相等就会以原子的方式将引用和标志都设置为新值;

原子更新数组

AtomicIntegerArray: 原子更新整型数组里地元素

AtomicLongArray: 原子更新长整型数组里的元素

AtomicReferenceArray: 原子更新引用类型数组里的元素

AtomicIntergerArray: 主要是提供原子的方式更新数组里的整型

//以原子方式将输入值与数组中索引i的元素相加

int addAndGet(int i, int delta);

//如果当前值等于预期值,则以原子方式将数组位置i的元素设置成update值

boolean compareAndSet(int i, int expect, int update);

AtomicIntergerArray会将当前数组复制一份,所以当AtomicIntegerArray对内部的元素进行修改时,不会影响传入的数组;

原子更新引用类型

AtomicReference: 原子更新引用类型

AtomicReferenceFieldUpdater: 原子更新引用类型里的字段

AtomicMarkableReference: 原子更新带有标记位的引用类型;可以原子的更新一个布尔类型的标记位和引用类型,构造方法是AtomicMarkableReference(V initialRef, boolean initialMark);

原子更新字段类

AtomicIntegerFieldUpdater: 原子更新整型的字段的更新器;

AtomicLongFieldUpdater: 原子更新长整型字段的更新器;

AtomicStampedReference: 原子更新带有版本号的引用类型;该类将整型数值与引用关联起来,可用于原子的更新数据和数据的版本号,可以解决使用CAS进行原子更新时可能出现的ABA问题;

使用AtomicStampedReference解决ABA:

public class UseAtomicIntegerTest {

static AtomicInteger integer = new AtomicInteger(1);

public static void main(String[] args) throws InterruptedException {

int oldValue = integer.get();

System.out.println(Thread.currentThread().getName() + " oldValue:" + oldValue);

Thread thread1 = new Thread(new Runnable() {

@Override

public void run() {

try {

System.out.println(Thread.currentThread().getName() + " --- " + integer.get());

Thread.sleep(2L);

} catch (InterruptedException e) {

e.printStackTrace();

}

}

});

Thread thread2 = new Thread(new Runnable() {

@Override

public void run() {

// 确保别的线程1先执行

Thread.yield();

// integer + 1

System.out.println(Thread.currentThread().getName() + " --- integer:" + integer.incrementAndGet());

// integer - 1

System.out.println(Thread.currentThread().getName() + " --- integer:" + integer.decrementAndGet());

}

});

thread1.start();

thread2.start();

thread1.join();

thread2.join();

System.out.println(Thread.currentThread().getName() + " --- " + integer.get());

}

}

打印结果如下:

main oldValue:1 marked:false

Thread-0 --- value:1 marked:false

Thread-1 --- value:2 result:true marked:true

Thread-1 --- value:2 marked:true

Thread-1 --- value:1 result:true marked:true

main --- 1 result:false

CAS存在以下的缺陷:

1.ABA问题。CAS需要在操作值的时候检查内存值是否发生变化,没有发生变化才会更新内存值。但是如果内存值原来是A,后来变成了B,然后又变成了A,那么CAS进行检查时会发现值没有发生变化,但是实际上是有变化的。ABA问题的解决思路就是在变量前面添加版本号,每次变量更新的时候都把版本号加一,这样变化过程就从“A-B-A”变成了“1A-2B-3A”;

JDK从1.5开始提供了AtomicStampedReference类来解决ABA问题,具体操作封装在compareAndSet()中。compareAndSet()首先检查当前引用和当前标志与预期引用和预期标志是否相等,如果都相等,则以原子方式将引用值和标志的值设置为给定的更新值;

2.循环时间长开销大。CAS操作如果长时间不成功,会导致其一直自旋,给CPU带来非常大的开销;

3.只能保证一个共享变量的原子操作。对一个共享变量执行操作时,CAS能够保证原子操作,但是对多个共享变量操作时,CAS是无法保证操作的原子性的;

Java从1.5开始JDK提供了AtomicReference类来保证引用对象之间的原子性,可以把多个变量放在一个对象里来进行CAS操作;

或者使用LongAdder替代;

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

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值