首页 > 编程语言 >【Java 并发】【八】【Atomic】【二】AtomicInteger、AtomicBoolean原理

【Java 并发】【八】【Atomic】【二】AtomicInteger、AtomicBoolean原理

时间:2023-04-03 23:22:43浏览次数:44  
标签:Java int unsafe value AtomicInteger 线程 Atomic public

1  前言

这节我们从AtomicInteger这个比较简单的原子类开始,来看看AtomicInteger的底层原理。

2  实测样例对比线程安全性

在说AtomicInteger的底层原理之前呢,我们先来看个例子感受下原子类:

static修饰的共享变量,我们开启两个线程对共享变量进行10000次+1的操作

2.1  Integer的测试样例

// 继承线程类
public class AtomicTest extends Thread {

    // 类变量看作共享变量
    private static int num = 0;

    // 线程工作内容  执行10000次+1
    @Override
    public void run() {
        for (int i = 0; i < 10000; i++) {
            num++;
        }
    }

    // main方法
    public static void main(String[] args) throws InterruptedException {
        // 两个线程分别去对共享变量执行10000次+1
        AtomicTest demo1 = new AtomicTest();
        AtomicTest demo2 = new AtomicTest();
        demo1.start();
        demo2.start();
        // 等待两个线程都执行完
        demo1.join();
        demo2.join();
        // 打印共享变量
        System.out.println("num = " + AtomicTest.num);
    }
}

2.2  AtomicInteger的测试样例

// 继承线程类
public class AtomicTest extends Thread {

    // 类变量看作共享变量
    private static AtomicInteger num = new AtomicInteger(0);

    // 线程工作内容  执行10000次+1
    @Override
    public void run() {
        for (int i = 0; i < 10000; i++) {
            num.addAndGet(1);
        }
    }

    // main方法
    public static void main(String[] args) throws InterruptedException {
        // 两个线程分别去对共享变量执行10000次+1
        AtomicTest demo1 = new AtomicTest();
        AtomicTest demo2 = new AtomicTest();
        demo1.start();
        demo2.start();
        // 等待两个线程都执行完
        demo1.join();
        demo2.join();
        // 打印共享变量
        System.out.println("num = " + AtomicTest.num);
    }
}

3  AtomicInteger原理

我们先通过源码来看一下AtomicInteger内部有哪些属性以及作用是什么:

public class AtomicInteger extends Number implements java.io.Serializable {
    // unsafe对象,可以直接根据内存地址操作数据,可以突破java语法的限制
    private static final Unsafe unsafe = Unsafe.getUnsafe();
    // 存储实际的值
    private volatile int value;
    // 存储value属性在AtomicInteger类实例内部的偏移地址
    private static final long valueOffset;
    static {
        try {
            // 在类初始化的时候就获取到了value变量在对象内部的偏移地址
            valueOffset = unsafe.objectFieldOffset
                (AtomicInteger.class.getDeclaredField("value"));
        } catch (Exception ex) { throw new Error(ex); }
    }
}

(1)首先内部持有一个unsafe对象的实例,Atomic原子类底层的操作都是基于unsafe对象来进行的

(2)然后有一个volatile int value变量,这个value就是原子类实际数值,使用volatile来修饰volatile可以保证并发中的可见性有序性(这里之前讲过volatile可以保证可见性和有序性,不记得的要回去重新看一下哦)

(3)还有一个valueOffset,看看这段代码,其实就是获得value属性在AtomicInteger对象内部的偏移地址的:

valueOffset = unsafe.objectFieldOffset
                (AtomicInteger.class.getDeclaredField("value"));

这个value属性相对于AtomicInter对象的内部偏移量存储在valueOffset中,我们之前讲过的,通过unsafe类是直接在内存级别去给变量赋值的。这里啊,我们再回顾一下unsafe值怎么从内存级别操作数据的:

首先要知道你要操作对象的内存地址,也就是AtomicInteger对象引用指向的内存地址

其次是要知道value属性在对象内部的偏移量offset,就可以通过(对象地址 + offset偏移量)直接找到value变量在内存的地址是多少,然后就可以直接给这块内存赋值了。

可以看到AtomicInteger内部其实就是一个 volatile int value的属性、一个unsafe类、一个偏移地址就完事了,Atomic原子类就是对基础的类型进行了一下包装而已,使得他们是线程安全的。比如AtomicInteger要对int进行包装,它内部是有一个属性来存储int的值的。至于它其他两个属性valueOffset、unsafe是辅助实现并发安全的属性。

3.1  AtomicInteger的构造方法

我们再来看看AtomicInteger的构造方法源码:

public AtomicInteger(int initialValue) {
    value = initialValue;
}

public AtomicInteger() {
}

提供了两个构造方法,第一个是在创建AtomicInteger对象的时候直接给内存存储值的volatile int value设置初始化的值
第二个没有赋初始值,那默认就是0;

3.2  AtomicInteger方法的源码分析

3.2.1  getAndIncrement()方法源码

public final int getAndIncrement() {
    return unsafe.getAndAddInt(this, valueOffset, 1);
}

我们看到AtomicInteger的getAndIncrement()方法源码很简单,底层就是基于unsafe.getAndAddInt包装了一下,让我们继续看一下unsafe.getAndAddInt方法源码:

public final int getAndAddInt(Object o, long valueOffset, int x) {
    int expected;
    do {
        expected = this.getIntVolatile(o, valueOffset);
    } while(!this.compareAndSwapInt(o, valueOffset, expected, expected + x));
      
    return expected;
}

(1)首先(o + valueOffset)得到value变量在内存中的地址,然后根据地址直接取出value在主内存值,这个值记录为expected
(2)根据 (o + offsetSet)地址偏移量,expected期待的值跟当前内存的值进行对比,如果相等则CAS操作成功内存的值修改为 expected + x
(3)如果值不相等,则进入下一次循环,直到CAS操作成功为止。
(4)由于使用了volatile 修饰符修饰了value,所以一旦修改了别的线程能立马可见、同时volatile还是用内存屏障确保有序性
(5)所以上面的CAS操作确保了原子性,通过volatile确保可见性、有序性;线程安全的三个特性都满足了,上面的操作就是线程安全的。

3.2.2  compareAndSet()方法源码

public final boolean compareAndSet(int expect, int update) {
    return unsafe.compareAndSwapInt(this, valueOffset, expect, update);
}

底层也还是直接调用unsafe的compareAndSwapInt方法直接去修改,不过这里不同的是,只会执行一次CAS操作,即使失败了也不会重复CAS

3.2.3  其它方法源码

其它的方法,基本都是直接调用unsafe.getAndInt方法:

public final int getAndDecrement() {
    return unsafe.getAndAddInt(this, valueOffset, -1);
}

public final int getAndAdd(int delta) {
    return unsafe.getAndAddInt(this, valueOffset, delta);
}

4  小结

AtomicInteger基本都是调用unsafe的CAS操作确保原子性,然后使用volatile修饰变量,确保可见性和有序性,有理解不对的地方欢迎指正哈。

标签:Java,int,unsafe,value,AtomicInteger,线程,Atomic,public
From: https://www.cnblogs.com/kukuxjx/p/17284757.html

相关文章

  • 如何用java校验SQL语句的合法性?(提供五种解决方案)
    方案一:使用JDBCAPI中提供的Statement接口的execute()方法要在Java中校验SQL语句的合法性,可以使用JDBCAPI中提供的Statement接口的execute()方法。这个方法会尝试执行给定的SQL语句,如果SQL语句不合法,则会抛出一个SQLException异常。因此,我们可以利用这个异常来判断SQL语句的合法......
  • java学习日记20230404-String类
    String类String对象用于保存字符串,也就是一组字符序列;字符串常量对象使用双引号包括起来的字符序列字符串的字符使用unicode字符编码,一个字符(不区分字母还是汉字)占用两个字节String常用的构造器:newString();newString(Stringoriginal);newString(char[]a);newString(char[]......
  • 更改我们在Javaweb的运行启动方式
    之前之前,我都是利用上面的Tomcat的三角符号进行启动的更改1、下载插件在idea界面的右上角的设置按钮,选中其中的Plugins选项:然后会弹出一个界面,选中右上方的Marketplace,然后在下面的搜索框里面搜索MavenHelper:下载它,然后重启IDEA2、使用插件运行web右键项目名称,选中其中......
  • 剑指offer(Java)-数组中的逆序对(困难)
    题目:在数组中的两个数字,如果前面一个数字大于后面的数字,则这两个数字组成一个逆序对。输入一个数组,求出这个数组中的逆序对的总数。示例1:输入:[7,5,6,4]输出:5限制:0<=数组长度<=50000解题思路:这道题的核心在于归并排序,在归并排序的基础上进行求解逆序对。题解参......
  • 【Java 并发】【八】【Atomic】【一】JUC下的Atomic原子类体系概览
    1 前言这节我们就开始看看Atomic原子类系列,JUC包下提供的原子类底层的实现原理基本都是差不多的,都是基于volatile和CAS操作来保证线程安全的,我们后续会着重分析几个类。2  概览我们看下JUC下边都有哪些原子类:看上面的图形,我们使用红色圈中的那些,就是我们要着重讨论的,一共......
  • Java-Day-3(运算符 + 标识符 + 键盘输入)
    Java-Day-3运算符算术运算符关系运算符[比较运算符]逻辑运算符赋值运算符三元运算符位运算符[需要二进制基础]算术运算符+、-、*、/System.out.println(10.0/4);//2.5doubled=10/4;//2.0//数学公式有时不能硬搬,例如:摄氏温度=5/9*(华氏温......
  • 1006-HBase操作实战(JAVA API模式)
    一、准备阶段开发环境:hadoop: hadoop -2.4.0hbase: hbase -0.94.11-securityeclipse:JunoServiceRelease2二、创建 hbasedemo项目1、通过Eclipse创建一个新Java工程2、右击项目根目录,选择“Propertiesà>JavaBuildPathà>Libraryà> Add Ext......
  • 如何使用Java程序实现二叉数
    二叉树是一种重要的数据结构,它由一组节点组成,每个节点可以拥有最多两个子节点。使用Java可以很容易地实现一个二叉树。下面将介绍如何使用Java实现二叉树。二叉树的节点定义一个二叉树的节点可以定义为一个类,其中至少需要包含以下属性:节点值左子节点右子节点在Java中,我们......
  • Java多线程
    1.可见性、原子性和有序性问题多线程有三大特性,分别是可见性、原子性和有序性。1.1可见性  在单核时代,所有的线程都是在一颗CPU上执行,CPU缓存与内存的数据一致性容易解决。因为所有线程都是操作同一个CPU的缓存,一个线程对缓存的写,对另外一个线程来说一定是可见的。一个线程......
  • Java判断文件夹、文件是否存在,不存在则新建
    Java判断文件夹、文件是否存在,不存在则新建原文链接:https://blog.csdn.net/asfsdgdfgdf/article/details/1283162781、Java判断是否存在文件夹,不存在则新建Filefile=newFile("D:/test/filetest/test.txt");if(!file.getParentFile().exists()){file.getParentFile().......