Java并发源码之AtomicLong

版权声明:本文为博主原创文章,转载请注明出处,谢谢!

版权声明:本文为博主原创文章,转载请注明出处:http://blog.jerkybible.com/2017/11/17/Java并发源码之AtomicLong/

访问原文「Java并发源码之AtomicLong

并发计数的问题

我这里要老生常谈了,因为这个例子我感觉应该很多人都见过,就是两个线程并发计数,但是最后计数的结果和我们的期望不同,代码如下。

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
public static long i = 0;
public static void main(String[] args) throws Exception {
Thread thread1 = new Thread(){
@Override
public void run(){
for(int m = 0; m < 500000; m++){
i++;
}
}
};
Thread thread2 = new Thread(){
@Override
public void run(){
for(int m = 0; m < 500000; m++){
i++;
}
}
};
thread1.start();
thread2.start();
Thread.sleep(1000L);
System.out.println("i = " + i);
}

结果如下:

这段代码主要有两个线程,线程的主要工作是循环对i加1,按道理运行完的结果应该是1000000,但是基本上是无法完成的,得到的值往往比正确值小很多,这是并发编程正常会遇到的问题,因为两个线程在运行的过程中是不顺序的,两个线程可能对同一个i值进行操作,打个比方就是可能在i为5的时候两个线程都是在5的基础上操作,应该得到7但是结果却是6。这个问题怎么解决呢,只要使用long对应的AtomicLong就可以了,代码如下:

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
public static AtomicLong atomicI = new AtomicLong(0);
public static void main(String[] args) throws Exception {
Thread thread1 = new Thread() {
@Override
public void run() {
for (int m = 0; m < 500000; m++) {
atomicI.incrementAndGet();
}
}
};
Thread thread2 = new Thread() {
@Override
public void run() {
for (int m = 0; m < 500000; m++) {
atomicI.incrementAndGet();
}
}
};
thread1.start();
thread2.start();
Thread.sleep(1000L);
System.out.println("i = " + atomicI);
}

结果如下:

可以看到结果是正常的。

AtomicLong的魔法

AtomicLong究竟是如何做到多线程运行时对同一个数运算能得到正确结果的呢,他究竟有什么魔法。我们先从他的成员变量开始。下面是他的主要成员变量。我们看到了一个熟悉的身影,那就是Unsafe,没有读过我的《Unsafe类初探》可以看一下,这个类的一个主要作用是完成CAS操作。

CAS(compare and swap),解决多线程并行情况下使用锁造成性能损耗的一种机制,CAS操作包含三个操作数——内存位置(V)、预期原值(A)和新值(B)。如果内存位置的值与预期原值相匹配,那么处理器会自动将该位置值更新为新值。否则,处理器不做任何操作。无论哪种情况,它都会在CAS指令之前返回该位置的值。CAS有效地说明了:我认为位置V应该包含值A;如果包含该值,则将B放到这个位置;否则,不要更改该位置,只告诉我这个位置现在的值即可。

1
2
3
4
5
6
7
private static final Unsafe unsafe = Unsafe.getUnsafe();
// value的偏移
private static final long valueOffset;
// value为volatile变量,保证每次取值时始终为内存中最新的值
private volatile long value;

下面我们看一下incrementAndGet这个方法,可以看到这个方法最终是通过compareAndSwapLong这个原子操作完成的,也就是说多线程没有办法同时将var1中的值进行加var4的操作,必须严格按照顺序执行。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
/**
* 对当前值进行加1操作
*
* @return 更新后的值
*/
public final long incrementAndGet() {
return unsafe.getAndAddLong(this, valueOffset, 1L) + 1L;
}
// 对当前的值进行加var4的操作,值到成功
public final long getAndAddLong(Object var1, long var2, long var4) {
long var6;
do {
// 获取当前的值
var6 = this.getLongVolatile(var1, var2);
// 对当前的值进行CAS操作
} while(!this.compareAndSwapLong(var1, var2, var6, var6 + var4));
return var6;
}

总结一下

从上面的分析可以看到,AtomicLong能保证同步的原因是使用了CAS原子操作。类似的,所有的AtomicXXX都可以保证操作的同步。