文章基于jdk1.7,通过学习《Java并发编程的艺术》,对Java原子操作的理解
当程序更新一个变量时,如果多线程同时更新这个变量。可能得到期望之外的值,比如变量 i=1,A线程更新 i+1,B线程也更新 i+1,最后得到的可能不是3,而是2。这是因为线程A和B在更新变量 i 的时候 拿到的 i 都是 1,这就是线程不安全的操作,通常我们会使用synchronized 来解决这个问题,synchronized 会保证多线程不会同时更新变量 i 。
而Java从JDK 1.5开始提供了java.util.concurrent.atomic包,这个包中的原子操作类提供了一种用法简单、性能高效、线程安全地一个变量的方式。
因为变量的类型有很多种,所以Atomic包里一共提供了13个类,属于4中类型的原子更新方式,分别是原子更新基本类型、原子更新数组、原子更新引用和原子更新属性(字段)。Atomic包里面的类基本都是使用Unsafe实现的包装类。
使用原子的方式更新基本类型,Atomic包提供了以下3个类。
- AtomicBoolean:原子更新布尔类型
- AtomicInteger:原子更新整型
- AtomicLong:原子更新长整型
以上3个类提供的方法几乎一样。此处仅以AtomicInteger为例进行说明,其常用方法如下:
- int addAndGet(int dalta):以原子方式见刚输入的数值与实例中的值(AtomicIntger里的value)相加,并返回结果。
- boolean compareAndSet( int expect, int update):如果输入的数值等于期望值,则以原子方式将该值设置为输入的值。
- int getAndIncrement():以原子方式将当前值加1,注意,这里返回的是自增前的值。
- void lazySet(int newValue):最终会设置成newValue,使用lazySet设置值后,可能导致其他线程在之后的一小段时间内还是可以读到旧的值。
- int getAndSet( int newValue):以原子方式设置为newValue的值,并返回旧值。
1 | import java.util.concurrent.atomic.AtomicInteger; |
输出结果如下:
1 | >>0>>>3 |
在JDK1.7中,AtomicInteger的getAndIncrement是这样的 :
1 | public final int getAndIncrement() { |
而在JDK1.8中,是这样的:
1 | public final int getAndIncrement() { |
可以看出,在JDK1.7中, 依靠的是我们熟悉的CAS算法,首先先循环获取当前值,然后对当前值加1 得到新值,对 当前值和新值进行比较并替换,成功的话返回当前值。而在JDK1.8中,直接使用了Unsafe的getAndAddInt 方法,在JDK1.7的Unsafe中,没有此方法。 JDK1.8中是对CAS算法的增强。
在Java的基本类型中除了Atomic包中提供原子更新的基本类型外,还有char、float和double。那么这些在Atomic包中没有提供原子更新的基本类型怎么保证其原子更新呢? 从AtomicBoolean源码中我们可以得到答案:首先将Boolean转换为整型,然后使用comareAndSwapInt进行CAS,所以原子更新char、float、double同样可以以此实现。
欢迎关注我的公众号~ 搜索公众号: 翻身码农把歌唱 或者 扫描下方二维码: