一、AtomicInteger简介
AtomicInteger,应该是atomic框架中用得最多的原子类了。顾名思义,AtomicInteger是Integer类型的线程安全原子类,可以在应用程序中以原子的方式更新int值。
1. 创建AtomicInteger对象
先来看下AtomicInteger对象的创建。
AtomicInteger提供了两个构造器,使用默认构造器时,内部int类型的value值为0:
AtomicInteger atomicInt = new AtomicInteger();
AtomicInteger类的内部并不复杂,所有的操作都针对内部的int值——value,并通过Unsafe类来实现线程安全的CAS操作:
2. AtomicInteger的使用
来看下面这个示例程序:
public class Main {
public static void main(String[] args) throws InterruptedException {
AtomicInteger ai = new AtomicInteger();
List<Thread> list = new ArrayList<>();
for (int i = 0; i < 10; i++) {
Thread t = new Thread(new Accumlator(ai), "thread-" + i);
list.add(t);
t.start();
}
for (Thread t : list) {
t.join();
}
System.out.println(ai.get());
}
static class Accumlator implements Runnable {
private AtomicInteger ai;
Accumlator(AtomicInteger ai) {
this.ai = ai;
}
@Override
public void run() {
for (int i = 0, len = 1000; i < len; i++) {
ai.incrementAndGet();
}
}
}
}
上述代码使用了AtomicInteger的 incrementAndGet 方法,以原子的操作对int值进行自增,该段程序执行的最终结果为10000(10个线程,每个线程对AtomicInteger增加1000),如果不使用AtomicInteger,使用原始的int或Integer,最终结果值可能会小于10000(并发时读到了过时的数据或存在值覆盖的问题)。
我们来看下 incrementAndGet 内部:
内部调用了Unsafe类的 getAndAddInt 方法,以原子方式将value值增加1,然后返回增加前的原始值。
注意,上述是JDK1.8的实现,在JDK1.8之前,上述方法采用了自旋+CAS操作的方式:
public final int getAndIncrement() {
for (;;) {
int current = get();
int next = current + 1;
if (compareAndSet(current, next))
return current;
}
}
3. AtomicInteger的特殊方法说明
AtomicInteger中有一个比较特殊的方法—— lazySet :
lazySet 方法是 set 方法的不可见版本。什么意思呢?
我们知道通过volatile修饰的变量,可以保证在多处理器环境下的“可见性”。也就是说当一个线程修改一个共享变量时,其它线程能立即读到这个修改的值。volatile的实现最终是加了内存屏障:
- 保证写volatile变量会强制把CPU写缓存区的数据刷新到内存
- 读volatile变量时,使缓存失效,强制从内存中读取最新的值
- 由于内存屏障的存在,volatile变量还能阻止重排序
lazySet 内部调用了Unsafe类的 putOrderedInt 方法,通过该方法对共享变量值的改变,不一定能被其他线程立即看到。也就是说以普通变量的操作方式来写变量。
为什么会有这种奇怪方法?什么情况下需要使用lazySet呢?
考虑下面这样一个场景:
private AtomicInteger ai = new AtomicInteger();
lock.lock();
try
{
// ai.set(1);
}
finally
{
lock.unlock();
}
由于锁的存在:
- lock() 方法获取锁时,和volatile变量的读操作一样,会强制使CPU缓存失效,强制从内存读取变量。
- unlock() 方法释放锁时,和volatile变量的写操作一样,会强制刷新CPU写缓冲区,把缓存数据写到主内存
所以,上述ai.set(1)
可以用ai.lazySet(1)
方法替换:
由锁来保证共享变量的可见性,以设置普通变量的方式来修改共享变量,减少不必要的内存屏障,从而提高程序执行的效率。
二、类/接口说明
类声明
构造器
接口声明
方法声明 | 描述 |
---|---|
intaccumulateAndGet(intx,IntBinaryOperatoraccumulatorFunction) | 使用IntBinaryOperator对当前值和x进行计算,并更新当前值,返回计算后的新值 |
intaddAndGet(intdelta) | 以原子方式将给定值与当前值相加,返回相加后的新值 |
booleancompareAndSet(intexpect,intupdate) | 如果当前值==expect,则以原子方式将该值设置为给定的更新值(update) |
intdecrementAndGet() | 以原子方式将当前值减1,返回新值 |
intget() | 获取当前值 |
intgetAndAccumulate(intx,IntBinaryOperatoraccumulatorFunction) | 使用IntBinaryOperator对当前值和x进行计算,并更新当前值,返回计算前的旧值 |
intgetAndAdd(intdelta) | 以原子方式将给定值与当前值相加,返回旧值 |
intgetAndDecrement() | 以原子方式将当前值减1,返回旧值 |
intgetAndIncrement() | 以原子方式将当前值加1,返回旧值 |
intgetAndSet(intnewValue) | 以原子方式设置为给定值,并返回旧值 |
intgetAndUpdate(IntUnaryOperatorupdateFunction) | 使用IntBinaryOperator对当前值进行计算,并更新当前值,返回计算前的旧值 |
intincrementAndGet() | 以原子方式将当前值加1,返回新值 |
voidlazySet(intnewValue) | 设置为给定值,但不保证值的改变被其他线程立即看到 |
voidset(intnewValue) | 设置为给定值 |
intupdateAndGet(IntUnaryOperatorupdateFunction) | 使用IntBinaryOperator对当前值进行计算,并更新当前值,返回计算后的新值 |
booleanweakCompareAndSet(intexpect,intupdate) | weakCompareAndSet无法保证除操作目标外的其他变量的执行顺序(编译器和处理器为了优化程序性能而对指令序列进行重新排序),同时也无法保证这些变量的可见性。 |
三、其它原子类
与 AtomicInteger 类似的原子类还有 AtomicBoolean 和 AtomicLong ,底层都是通过Unsafe类做CAS操作,来原子的更新状态值。可以参考Oracle官方文档:https://docs.oracle.com/javase/8/docs/api/ ,不再赘述。
Java 面试宝典是大明哥全力打造的 Java 精品面试题,它是一份靠谱、强大、详细、经典的 Java 后端面试宝典。它不仅仅只是一道道面试题,而是一套完整的 Java 知识体系,一套你 Java 知识点的扫盲贴。
它的内容包括:
- 大厂真题:Java 面试宝典里面的题目都是最近几年的高频的大厂面试真题。
- 原创内容:Java 面试宝典内容全部都是大明哥原创,内容全面且通俗易懂,回答部分可以直接作为面试回答内容。
- 持续更新:一次购买,永久有效。大明哥会持续更新 3+ 年,累计更新 1000+,宝典会不断迭代更新,保证最新、最全面。
- 覆盖全面:本宝典累计更新 1000+,从 Java 入门到 Java 架构的高频面试题,实现 360° 全覆盖。
- 不止面试:内容包含面试题解析、内容详解、知识扩展,它不仅仅只是一份面试题,更是一套完整的 Java 知识体系。
- 宝典详情:https://www.yuque.com/chenssy/sike-java/xvlo920axlp7sf4k
- 宝典总览:https://www.yuque.com/chenssy/sike-java/yogsehzntzgp4ly1
- 宝典进展:https://www.yuque.com/chenssy/sike-java/en9ned7loo47z5aw
目前 Java 面试宝典累计更新 400+ 道,总字数 42w+。大明哥还在持续更新中,下图是大明哥在 2024-12 月份的更新情况:
想了解详情的小伙伴,扫描下面二维码加大明哥微信【daming091】咨询
同时,大明哥也整理一套目前市面最常见的热点面试题。微信搜[大明哥聊 Java]或扫描下方二维码关注大明哥的原创公众号[大明哥聊 Java] ,回复【面试题】 即可免费领取。