1. 引子
在之前《CountDownLatch和Semaphore》一章最后的例子留下了一个问题,即多线程并发访问时出现不一致的线程不安全问题,为此本节将对其进行修改以满足线程安全的要求,并引入atomic包中的AtomicInteger类的相关介绍。
2. 使用AtomicInteger确保一致性
对示例代码修改如下:
public class ConcurrencyTest {
// 请求总数
public static int clientTotal = 5000;
// 同时并发执行的线程数
public static int threadTotal = 200;
// public static int count = 0; // 存在线程安全问题
public static AtomicInteger count = new AtomicInteger(0);
public static void main(String[] args) throws InterruptedException {
// 创建线程池
ExecutorService executorService = Executors.newCachedThreadPool();
// 信号量
final Semaphore semaphore = new Semaphore(threadTotal);
// 计数器闭锁
final CountDownLatch countDownLatch = new CountDownLatch(clientTotal);
for (int i=0;i<clientTotal;i++){
executorService.execute(new Runnable() {
@Override
public void run() {
try {
semaphore.acquire();
add();
semaphore.release();
} catch (Exception e){
System.out.println(e);
}
// 每个线程执行时计数器都减1
countDownLatch.countDown();
}
});
}
System.out.println("countDownLatch is awaiting!");
countDownLatch.await();
executorService.shutdown();
System.out.println("count="+count.get());
}
public static void add(){
//count ++;
count.getAndIncrement();
}
}
从上述示例可知,我们修改了如下内容:
- 修改count变量从int改为AtomicInteger。
- count自加由
count++
改为count.getAndIncrement();
在改之前可知由于具有一致性问题导致最终结果不是理想中的5000,经过修改后,每次结果都符合预期,即5000的值。那么AtomicInteger究竟是如何解决一致性问题的呢,下面对其进行分析。
3. AtomicInteger源码分析
要想理解其实现原理,主要分析下count.getAndIncrement();
方法实现,源码如下:
private static final Unsafe unsafe = Unsafe.getUnsafe();
public final int getAndIncrement() {
return unsafe.getAndAddInt(this, valueOffset, 1);
}
实现主要调用了Unsafe类的getAndAddInt()方法,传入第一个参数为当前对象count,第一个参数为当前对象的值,比如当前循环中count值为2,第三个参数为每次递增1。进入这个方法:
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;
}
public native int getIntVolatile(Object var1, long var2);
public final native boolean compareAndSwapInt(Object var1, long var2, int var4, int var5);
getAndAddInt()方法中涉及到的两个方法调用如上都定义为native,即java底层实现的本地方法(JNI),这里就不深入查看实现了,主要说下做了什么:
- getIntVolatile()方法:获取保存当前对象count的主存地址的引用(注意不是对象的值,是引用)。
- compareAndSwapInt()方法:比较当前对象的值和底层该对象的值是否相等,如果相等,则将当前对象值加1,如果不相等,则重新去获取底层该对象的值,这个方法的实现就是CPU的CAS(compare and swap)操作。
- 写到这里我一直有个疑问,那就是这个AtomicInteger和volatile有什么区别呢?在之前的学习中我们可知volatile具有一致性,但是不具备原子性,但是AtomicInteger却号称同时具备一致性和原子性,这是什么原因呢?我又看了下AtomicInteger源码中实际保存数据的变量定义:
private volatile int value;
。原来AtomicInteger内部实现就使用了volatile关键字,这就解释了为什么执行CAS操作的时候,从底层获取的数据就是最新的数据,如果当前要保存的值和内存中最新的值不相等的话,说明在这个过程中被其他线程修改了,只能获取更新当前值为最新值,再那这个当前值再去和重新去内存获取的最新值比较,直到二者相等的时候,才完成+1的过程。 - 使用AtomicInteger的另外一个好处在于,它不同于sychronized关键字或lock用锁的形式来实现原子性,因为加锁会影响性能,而是采用循环比较的形式来提高性能。
网友评论