IT数码 购物 网址 头条 软件 日历 阅读 图书馆
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
图片批量下载器
↓批量下载图片,美女图库↓
图片自动播放器
↓图片自动播放器↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁
 
   -> Java知识库 -> juc-atomic 原子类框架之AtomicInteger -> 正文阅读

[Java知识库]juc-atomic 原子类框架之AtomicInteger

早期的JDK版本中,如果要并发的对Integer、Long、Double之类的Java原始类型或引用类型进行操作,一般都需要通过锁来控制并发,以防数据不一致。

从JDK1.5开始,引入了java.util.concurrent.atomic工具包,该包提供了许多Java原始/引用类型的映射类,如AtomicIntegerAtomicLongAtomicBoolean,这些类可以通过一种“无锁算法”,线程安全的操作Integer、Long、Boolean等原始类型。

所谓“无锁算法”,我们在讲juc-locks锁框架系列中,已经接触过太多次了,其实底层就是通过Unsafe类实现的一种比较并交换的算法CAS,大致的结构如下(具体入参,根据上下文有所不同):
boolean compareAndSet(expectedValue, updateValue);
当希望修改的值与expectedValue相同时,则尝试将值更新为updateValue,更新成功返回true,否则返回false。

java.util.concurrent.atomic包结构如下:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-THIXfHxF-1643903610412)(C:\Users\14470\Desktop\java笔记\java JUC\J.U.C之atomic框架.assets\image-20220203210529126.png)]

2. AtomicInteger的使用

AtomicInteger,应该是atomic框架中用得最多的原子类了。顾名思义,AtomicInteger是Integer类型的线程安全原子类,可以在应用程序中以原子的方式更新int值。

来看下面这个示例程序:

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();
            }
        }
    }
}

结果:10000

可以看出我们循环创建了10个线程对AtomicInteger变量ai.incrementAndGet();操作。

即以原子的操作对int值进行自增。如果不使用AtomicInteger,使用原始的int或Integer,最终结果值可能会小于10000(并发时读到了过时的数据或存在值覆盖的问题)。

它里面有很多类似的方法,大部分都是基于Unsafe类实现的,实现了原子操作。

(Unsafe类详解可见我的另一篇文章JAVA中的Unsafe类看着一篇就够了。CAS操作原理详见:并发编程的基石CAS

源码解读

它有了两种构造方法

有参构造器

public AtomicInteger(int initialValue)

用给定的初始值创建一个新的AtomicInteger。

  • 参数

    initialValue - 初始值

无参构造器

public AtomicInteger()

创建一个新的AtomicInteger,初始值为 0

源码摘录:

// 设置为使用 Unsafe.compareAndSwapInt 进行更新
private static final Unsafe unsafe = Unsafe.getUnsafe();//获取Unsafe实例
private static final long valueOffset;

//静态初始化块
static {
    try {
        valueOffset = unsafe.objectFieldOffset
            (AtomicInteger.class.getDeclaredField("value"));
    } catch (Exception ex) { throw new Error(ex); }
}

AtomicInteger中大部分操作都依靠Unsafe类完成,所以他一上来就直接Unsafe.getUnsafe()获取Unsafe实例。

valueOffset这个是指类中相应字段在该类的偏移量,在这里具体即是指value这个字段在AtomicInteger类的内存中相对于该类首地址的偏移量。

`一个对象一创建,然后给他分配内存空间,这样他就有一个开始的地址,比如是000100。然后对象里面有一堆属性,他们也需要空间呀。
那么他们就在 地址000100 后面挨个分配好自己的空间,比如参数A就分配到了000111地址。
那么valueOffset方法就可以获取到地址偏移量:000111-000100=000011
后面我们就可以用一些方法用地址偏移量从起始地址出发获取到这个参数A了。

然后可以看一个有一个静态初始化块,这个块的作用即是求出value这个字段的偏移量。具体的方法使用的反射的机制得到valueField对象,再根据objectFieldOffset这个方法求出value这个变量内存中在该对象中的偏移量。

因为其中的方法大同小异,基本都是依靠Unsafe的CAS操作方法来实现的,所以这里就列举两个方法来讲解。

方法一:AtomicInteger.incrementAndGet()

看源码:

/**
    以原子方式将当前值加一。
    return :更新的值
 */
public final int incrementAndGet() {
    return unsafe.getAndAddInt(this, valueOffset, 1) + 1;
}

他onlyonlyonly调用了unsafe的getAndAddInt() 方法。

public final int getAndAddInt(Object var1, long var2, int var4) {
    int var5;
    do {
        var5 = this.getIntVolatile(var1, var2)//正确的读取咱们的int值。
    } while(!this.compareAndSwapInt(var1, var2, var5, var5 + var4));//一直循环去CAS更新直到成功

    return var5;//返回更新后的值
}

getIntVolatile方法用于在对象指定偏移地址处volatile读取一个intvolatile读写可以保证可见性和有序性。(详见Unsafe和并发编程的基石CAS)

方法二:AtomicInteger的特殊方法

AtomicInteger中有一个比较特殊的方法——lazySet

看源码

private volatile int value;
/**
 * 设置为给定值
 *
 * @param  newValue – 新值
 */
public final void set(int newValue) {
    value = newValue;
}

/**
 * 最终设置为给定值。
 *  参数:newValue – 新值
 * @since JDK1.6
 */
public final void lazySet(int newValue) {
    unsafe.putOrderedInt(this, valueOffset, newValue);
}

可以看到value是用volatile修饰的。我们知道通过volatile修饰的变量,可以保证在多处理器环境下的“可见性”。也就是说当一个线程修改一个共享变量时,其它线程能立即读到这个修改的值。volatile的实现最终是加了内存屏障:

  1. 保证写volatile变量会强制把CPU写缓存区的数据刷新到内存
  2. 读volatile变量时,使缓存失效,强制从内存中读取最新的值
  3. 由于内存屏障的存在,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)方法替换:

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

再再其他方法:

三、其它原子类

AtomicInteger类似的原子类还有AtomicBooleanAtomicLong,底层都是通过Unsafe类做CAS操作,来原子的更新状态值。
相信大家伙一定可以举一反三。

  Java知识库 最新文章
计算距离春节还有多长时间
系统开发系列 之WebService(spring框架+ma
springBoot+Cache(自定义有效时间配置)
SpringBoot整合mybatis实现增删改查、分页查
spring教程
SpringBoot+Vue实现美食交流网站的设计与实
虚拟机内存结构以及虚拟机中销毁和新建对象
SpringMVC---原理
小李同学: Java如何按多个字段分组
打印票据--java
上一篇文章      下一篇文章      查看所有文章
加:2022-02-04 10:54:21  更:2022-02-04 10:54:40 
 
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁

360图书馆 购物 三丰科技 阅读网 日历 万年历 2024年11日历 -2024/11/24 10:55:58-

图片自动播放器
↓图片自动播放器↓
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
图片批量下载器
↓批量下载图片,美女图库↓
  网站联系: qq:121756557 email:121756557@qq.com  IT数码