原子更新基本类型类

文章基于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
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
import java.util.concurrent.atomic.AtomicInteger;
/**
* @author: xbq
* @date: 2018/10/6 18:31
* @description:
*/
public class AtomicIntegerTest {

static AtomicInteger atomicInteger = new AtomicInteger(2);

public static void main(String[] args) {
// addAndGet 以原子方式将输入的数值与实例中的数值相加,并返回结果
System.out.println(">>0>>>" + atomicInteger.addAndGet(1));
// 如果输入的数值等于预期值,则以原子方式将该值设置为输入的值
System.out.println(">>1>>>" + atomicInteger.compareAndSet(3, 8080));
// 获取值
System.out.println(">>2>>>" + atomicInteger.get());
// 以原子方式将当前值加1,返回的值 为自增前的值
System.out.println(">>3>>>" + atomicInteger.getAndIncrement());
// 获取值
System.out.println(">>4>>>" + atomicInteger.get());
// 以原子方式将当前值加1,返回的值 为自增后的值
System.out.println(">>5>>>" + atomicInteger.incrementAndGet());
// 获取值
System.out.println(">>6>>>" + atomicInteger.get());
// 以原子方式设置为newValue的值,并返回旧值
System.out.println(">>7>>>" + atomicInteger.getAndSet(86));
// 获取值
System.out.println(">>8>>>" + atomicInteger.get());
// 最终会设置成newValue,使用lazySet设置值后,可能导致其他线程在之后的一小段时间内还是可以读到旧的值
atomicInteger.lazySet(10);
System.out.println(">>9>>>" + atomicInteger.get());
}
}

输出结果如下:

1
2
3
4
5
6
7
8
9
10
>>0>>>3
>>1>>>true
>>2>>>8080
>>3>>>8080
>>4>>>8081
>>5>>>8082
>>6>>>8082
>>7>>>8082
>>8>>>86
>>9>>>10

在JDK1.7中,AtomicInteger的getAndIncrement是这样的 :

1
2
3
4
5
6
7
8
9
10
11
12
public final int getAndIncrement() {
for (;;) {
int current = get();
int next = current + 1;
if (compareAndSet(current, next))
return current;
}
}

public final boolean compareAndSet(int expect, int update) {
return unsafe.compareAndSwapInt(this, valueOffset, expect, update);
}

而在JDK1.8中,是这样的:

1
2
3
public final int getAndIncrement() {
return unsafe.getAndAddInt(this, valueOffset, 1);
}

可以看出,在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同样可以以此实现。

欢迎关注我的公众号~ 搜索公众号: 翻身码农把歌唱 或者 扫描下方二维码:

img

坚持原创技术分享,您的支持将鼓励我继续创作!
-------------本文结束感谢您的阅读-------------
分享到:
0%