线程安全性-原子性
1.Atomic包
Atomic包简单使用,能有效的确保数据的原子性,此处用AtomicInteger来进行计数(其他的都差不多)
//默认计数器的值为0
public static AtomicLong count = new AtomicLong(0);
//同时并发执行的线程数
public static int threadTotal = 200;
//请求总数
public static int clientTotal = 5000;
public static void main(String[] args) throws InterruptedException {
//创建线程池
ExecutorService executorService = Executors.newCachedThreadPool();
//计数信号量 每个acquire方法阻塞,直到有一个许可证可以获得然后拿走一个许可证;每个release方法增加一个许可证,这可能会释放一个阻塞的acquire方法。
final Semaphore semaphore = new Semaphore(threadTotal);
final CountDownLatch countDownLatch = new CountDownLatch(clientTotal);
for(int i = 0; i < clientTotal; i++){
executorService.execute(()->{
try {
//阻塞
semaphore.acquire();
add();
//释放
semaphore.release();
//计数器减一
countDownLatch.countDown();
} catch (Exception e) {
log.error("exception",e);
}
});
}
//线程阻塞,等待计数器减为0释放
countDownLatch.await();
executorService.shutdown();
log.info("count:{}",count.get());
}
private static void add(){
//相当于++count
count.incrementAndGet();
//相当于count++
//count.getAndIncrement();
}
执行完结果为5000,没有出现线程问题,接下看,我们来分析一下atomic的incrementAndGet()方法是如何保证线程的原子性的。
/**
* Atomically increments by one the current value.
*
* @return the updated value
*/
public final long incrementAndGet() {
return unsafe.getAndAddInt(this, valueOffset, 1L) + 1L;
}
/**
* var1:传入的对象 var2:当前的值
* var4:需要增加的值 var6:当前内存中存在的值
*/
public final long getAndAddLong(Object var1, long var2, long var4) {
long var6;
do {
//在内存中取出当前的值
var6 = this.getIntVolatile(var1, var2);
//this.compareAndSwapLong(var1, var2, var6, var6 + var4) 将传入的var2与取出的var6进行对比,如果值相同,说明其他线程没有进行修改,则进行修改操作并退出循环,如果值不同,继续取出数据进行比较
} while(!this.compareAndSwapInt(var1, var2, var6, var6 + var4));
return var6;
}
总结:
在并发较低的情况下使用atmoic可以提高效率,一旦并发高了以后,执行unsafe.getAndAddInt()方法时,可能会循环多次取出值来进行判断,导致性能降低。并发高时,可以使用LongAdder类来进行处理。