目录
一. Atomic基本类型
1.基本数据类型
- AtomicInteger
- AtomicLong
- AtomicBoolean
2.常用方法
- int get() // 获取当前值
- int getAndSet(int value) // 获取当前值并设置成指定值等价于普通变量的i = value
- int getAndIncrement() // 自增 ,等价于普通变量的i++
- int getAndDecrement() // 自减,等价于普通变量的i–
- int getAndAdd(int Value) // 加上预期值,等价于普通变量的i = i + value
- boolean compareAndSet(int expect, int update) //如果该对象等于期望值,把它更新成update的值,等价于普通变量的`if (i == expect) {i = update}
二. AtomicInteger
- AtomicInteger 底层采用的是CAS原理(CompareAndSwap)。AtomicInteger 类型的对象,之所以在对AtomicInteger对象操作时,能保证线程安全,是因为在进行操作时,会将当前对象的值与内存中变量的值进行比较,如果是一致的,则进行变量操作,否则继续去内存中获取变量的值,直至当前值,与内存中的值是一致的。
- AtomicInteger 对象,可以通过调用incrementAndGet()方法和getAndIncrement()方法进行值加1的操作,区别在于:incrementAndGet()方法先执行加1操作,然后获取值;getAndIncrement()方法是先获取值然后执行加1的操作;
三. AtomicInteger 对象使用示例:
1.非线程安全的使用示例:
@Slf4j
public class CountExample1 {
// 请求总数
public static int clientTotal = 5000;
// 同时并发执行的线程数
public static int threadTotal = 200;
public static int count = 0;
public static void main(String[] args) throws Exception {
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(() -> {
try {
semaphore.acquire();
add();
semaphore.release();
} catch (Exception e) {
log.error("exception", e);
}
countDownLatch.countDown();
});
}
countDownLatch.await();
executorService.shutdown();
log.info("count:{}", count);
}
private static void add() {
count++;
}
}
上述代码每次执行过程中,输出的count
值都有可能会不相同,而且小于5000,这是因为count++
操作时,会发生并发问题;
2. 使用AtomicInteger 实现线程安全的使用示例:
@Slf4j
public class AtomicExample1 {
// 请求总数
public static int clientTotal = 5000;
// 同时并发执行的线程数
public static int threadTotal = 200;
//创建AtomicInteger对象并初始化
public static AtomicInteger count = new AtomicInteger(0);
public static void main(String[] args) throws Exception {
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(() -> {
try {
semaphore.acquire();
add();
semaphore.release();
} catch (Exception e) {
log.error("exception", e);
}
countDownLatch.countDown();
});
}
countDownLatch.await();
executorService.shutdown();
log.info("count:{}", count.get());
}
private static void add() {
count.incrementAndGet(); //先增加后获取
// count.getAndIncrement(); //先获取后增加
}
}
3. AtomicInteger线程安全底层实现
上述AtomicInteger对象执行incrementAndGet()方法时,底层方法是:
/**
* Atomically increments by one the current value.
*
* @return the updated value
*/
public final int incrementAndGet() {
return unsafe.getAndAddInt(this, valueOffset, 1) + 1;
}
其中getAndAddInt()方法的源码如下:
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;
}
其中compareAndSwapInt()
方法是CAS机制中的一种,它的源码如下:
public final native boolean compareAndSwapInt(Object var1, long var2, int var4, int var5);
由于compareAndSwapInt()
方法是被native
关键字修饰,说明这个方法是原生函数,也就是这个方法是用C/C++语言实现的,并且被编译成了DLL,由java去调用。这些函数的实现体在DLL中,JDK的源代码中并不包含,我们是看不到的。
getAndAddInt()
方法执行过程如下这篇文章所示:getAndAddInt()执行过程
二、原子类的ABA问题
原因:
- 所谓ABA就是一个线程在CAS更新变量的时候,该变量已经被别的线程先从A该成了B,再从B该回了A,此时线程还是会CAS成功,因为它认为期望值并没有变
解决办法:
- 解决办法是增加版本号,每次修改版本号加1
- 具体解决方法:使用版本号功能的类为
AtomicStampedReference
Java并发编程学习系列
如有帮助,烦请点赞收藏一下啦 (◕ᴗ◕✿)