资讯专栏INFORMATION COLUMN

Java多线程进阶(十三)—— J.U.C之atomic框架:AtomicInteger

darkbug / 1514人阅读

摘要:顾名思义,是类型的线程安全原子类,可以在应用程序中以原子的方式更新值。创建对象先来看下对象的创建。也就是说当一个线程修改一个共享变量时,其它线程能立即读到这个修改的值。

本文首发于一世流云的专栏:https://segmentfault.com/blog...
一、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 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)方法替换:

由锁来保证共享变量的可见性,以设置普通变量的方式来修改共享变量,减少不必要的内存屏障,从而提高程序执行的效率。

二、类/接口说明 类声明

构造器

接口声明
方法声明 描述
int accumulateAndGet(int x, IntBinaryOperator accumulatorFunction) 使用IntBinaryOperator 对当前值和x进行计算,并更新当前值,返回计算后的新值
int addAndGet(int delta) 以原子方式将给定值与当前值相加,返回相加后的新值
boolean compareAndSet(int expect, int update) 如果当前值 == expect,则以原子方式将该值设置为给定的更新值(update)
int decrementAndGet() 以原子方式将当前值减 1,返回新值
int get() 获取当前值
int getAndAccumulate(int x, IntBinaryOperator accumulatorFunction) 使用IntBinaryOperator 对当前值和x进行计算,并更新当前值,返回计算前的旧值
int getAndAdd(int delta) 以原子方式将给定值与当前值相加,返回旧值
int getAndDecrement() 以原子方式将当前值减 1,返回旧值
int getAndIncrement() 以原子方式将当前值加 1,返回旧值
int getAndSet(int newValue) 以原子方式设置为给定值,并返回旧值
int getAndUpdate(IntUnaryOperator updateFunction) 使用IntBinaryOperator 对当前值进行计算,并更新当前值,返回计算前的旧值
int incrementAndGet() 以原子方式将当前值加 1,返回新值
void lazySet(int newValue) 设置为给定值,但不保证值的改变被其他线程立即看到
void set(int newValue) 设置为给定值
int updateAndGet(IntUnaryOperator updateFunction) 使用IntBinaryOperator 对当前值进行计算,并更新当前值,返回计算后的新值
boolean weakCompareAndSet(int expect, int update) weakCompareAndSet无法保证除操作目标外的其他变量的执行顺序( 编译器和处理器为了优化程序性能而对指令序列进行重新排序 ),同时也无法保证这些变量的可见性。
三、其它原子类

AtomicInteger类似的原子类还有AtomicBooleanAtomicLong,底层都是通过Unsafe类做CAS操作,来原子的更新状态值。可以参考Oracle官方文档:https://docs.oracle.com/javas...,不再赘述。

文章版权归作者所有,未经允许请勿转载,若此文章存在违规行为,您可以联系管理员删除。

转载请注明本文地址:https://www.ucloud.cn/yun/76561.html

相关文章

  • Java线程进阶(一)—— J.U.C并发包概述

    摘要:整个包,按照功能可以大致划分如下锁框架原子类框架同步器框架集合框架执行器框架本系列将按上述顺序分析,分析所基于的源码为。后,根据一系列常见的多线程设计模式,设计了并发包,其中包下提供了一系列基础的锁工具,用以对等进行补充增强。 showImg(https://segmentfault.com/img/remote/1460000016012623); 本文首发于一世流云专栏:https...

    anonymoussf 评论0 收藏0
  • Java线程进阶(十五)—— J.U.Catomic框架Atomic数组

    摘要:注意原子数组并不是说可以让线程以原子方式一次性地操作数组中所有元素的数组。类的方法返回指定类型数组的元素所占用的字节数。,是将转换为进制,然后从左往右数连续的个数。 showImg(https://segmentfault.com/img/remote/1460000016012145); 本文首发于一世流云的专栏:https://segmentfault.com/blog... 一...

    lunaticf 评论0 收藏0
  • Java线程进阶(十六)—— J.U.Catomic框架:FieldUpdater

    摘要:所谓,就是可以以一种线程安全的方式操作非线程安全对象的某些字段。我们来对上述代码进行改造账户类改造引入通过操作字段调用方,并未做任何改变上述代码,无论执行多少次,最终结果都是,因为这回是线程安全的。这也是整个包的设计理念之一。 showImg(https://segmentfault.com/img/remote/1460000016012109); 本文首发于一世流云的专栏:http...

    darcrand 评论0 收藏0
  • Java线程进阶(十二)—— J.U.Catomic框架:Unsafe类

    摘要:本身不直接支持指针的操作,所以这也是该类命名为的原因之一。中的许多方法,内部其实都是类在操作。 showImg(https://segmentfault.com/img/remote/1460000016012251); 本文首发于一世流云的专栏:https://segmentfault.com/blog... 一、Unsafe简介 在正式的开讲 juc-atomic框架系列之前,有...

    赵连江 评论0 收藏0
  • Java线程进阶(十四)—— J.U.Catomic框架AtomicReference

    摘要:但是,有些操作会依赖于对象的变化过程,此时的解决思路一般就是使用版本号。在变量前面追加上版本号,每次变量更新的时候把版本号加一,那么就会变成。四的引入就是上面所说的加了版本号的。 showImg(https://segmentfault.com/img/remote/1460000016012188); 本文首发于一世流云的专栏:https://segmentfault.com/blo...

    aboutU 评论0 收藏0

发表评论

0条评论

最新活动
阅读需要支付1元查看
<