首页 > 编程语言 >AtomicInteger源码解读和Unsafe对象

AtomicInteger源码解读和Unsafe对象

时间:2023-10-08 17:12:50浏览次数:41  
标签:count Thread int Unsafe AtomicInteger cas 源码 public

针对线程安全问题,jdk除提供了加锁的解决方式外还提供了无锁的方式,例如AtomicInteger 这个原子整数类,

无锁并发的线程安全是通过cas来实现的,这一篇文章就来简单分析下AtomicInteger 的源码实现。

一、AtomicInteger的简答使用

先来看一断非线程安全的代码

@Slf4j
public class ThreadTest2 {

    static int count=0;

    public static void main(String[] args) throws InterruptedException {
        /**
         * 有一个静态变量count,两个线程分别对其进行相等次数的+1和-1操作,
         * 因为++和--操作本身不是原子的,所以最终打印出的res可能不是0,
         * 为了看到效果让这段代码整体重复运行100次
         */
        for (int k = 0; k < 100; k++) {
            Thread t1 = new Thread(new Runnable() {
                @Override
                public void run() {
                    for (int i = 0; i < 10; i++) {
                        count++;
                        try {
                            Thread.sleep(1);
                        } catch (InterruptedException e) {
                            e.printStackTrace();
                        }
                    }
                }
            });

            Thread t2 = new Thread(new Runnable() {
                @Override
                public void run() {
                    for (int i = 0; i < 10; i++) {
                        count--;
                        try {
                            Thread.sleep(2);
                        } catch (InterruptedException e) {
                            e.printStackTrace();
                        }
                    }
                }
            });

            t1.start();
            t2.start();
            t1.join();
            t2.join();

            log.info("res:{}",count);
        }
    }
}

为了解决上述代码中多个线程对变量count同时操作的线程安全问题,可以使用加锁的方式去解决,但jdk也提供了一种无锁的方式,即使用原子整数AtomicInteger 来作为count,我们先来看下怎么使用

@Slf4j
public class ThreadTest2 {

    //使用AtomicInteger作为计数器
    static AtomicInteger count= new AtomicInteger(0);

    public static void main(String[] args) throws InterruptedException {
        /**
         * 使用AtomicInteger作为计数器,两个线程分别对其进行相等次数的+1和-1操作,
         * 因为++和--操作本身不是原子的,所以最终打印出的res可能不是0,
         * 为了看到效果让这段代码整体重复运行100次
         */
        for (int k = 0; k < 100; k++) {
            Thread t1 = new Thread(new Runnable() {
                @Override
                public void run() {
                    for (int i = 0; i < 10; i++) {
                        //原子整数的自增操作
                        count.getAndIncrement();
                        try {
                            Thread.sleep(1);
                        } catch (InterruptedException e) {
                            e.printStackTrace();
                        }
                    }
                }
            });

            Thread t2 = new Thread(new Runnable() {
                @Override
                public void run() {
                    for (int i = 0; i < 10; i++) {
                        //原子整数的自减操作
                        count.getAndDecrement();
                        try {
                            Thread.sleep(2);
                        } catch (InterruptedException e) {
                            e.printStackTrace();
                        }
                    }
                }
            });

            t1.start();
            t2.start();
            t1.join();
            t2.join();

            log.info("res:{}",count);
        }
    }
}

上边的代码中多线程同时调用原子整数类提供的自增和自减操作,由原子整数类保证了线程安全,即

getAndIncrement/getAndDecrement这两个操作本身就是原子的。

二、 AtomicInteger源码解读

为什么AtomicInteger的自增和自减操作是原子的可以保证线程安全呢?这是因为它使用了Unsafe 对象

我们来看一下它的源码

public class AtomicInteger extends Number implements java.io.Serializable {
    private static final long serialVersionUID = 6214790243416807050L;

    // setup to use Unsafe.compareAndSwapInt for updates
    // Unsafe对象是jdk提供的,其中提供了一些直接操作内存的方法和一些cas方法可以用来控制线程安全
    private static final Unsafe unsafe = Unsafe.getUnsafe();
    private static final long valueOffset;

    static {
        try {
            // 这里是用Unsafe获取当前类中value这个属性的内存偏移量,
            //可以理解成每个对象都会有一个内存地址,而对象中属性的内存地址相对对象本身的偏移量是固定的,
            // 知道了一个对象的内存地址,再加上某个属性的地址偏移量就能定位到某个具体的属性,
            //这里获取这个内存偏移量是为了后续通过Unsafe类直接操作这个属性
            valueOffset = unsafe.objectFieldOffset
                (AtomicInteger.class.getDeclaredField("value"));
        } catch (Exception ex) { throw new Error(ex); }
    }

    // 原子整数类的value属性
    private volatile int value;
    
    //构造方法
    public AtomicInteger(int initialValue) {
        value = initialValue;
    }
    
    //自增操作,可以看到是直接调用的unsafe类的方法,传递了当前对象,内存偏移量,
    public final int getAndIncrement() {
        return unsafe.getAndAddInt(this, valueOffset, 1);
    }
    
    //自减操作
    public final int getAndDecrement() {
        return unsafe.getAndAddInt(this, valueOffset, -1);
    }
}

从源码可以看到原子整数类中实现原子自增的关键代码就是调了unsafe类的方法,所以有必要再看下这个类

三、Unsafe类

Unsafe类是java中的一个比较底层的类,其中包含了一些比较底层的操作,例如直接操作内存,cas操作,线程操作等。因为这是一个比较底层的类,所以jdk不允许我们直接使用它,要获取到它的对象只能通过反射来获取到,我们先看下它的源码,了解下上边提到的unsafe.getAndAddInt方法

部分源码,这个类是sun包下的一个类,从这个包名也能看出是一个比较底层的类,如非必要不要自己去使用它。

原子整数类的线程安全是通过unsafe提供的cas操作完成的,我们先看下它提供的原子操作方法,

getAndAddInt方法就是通过调用cas操作+自旋的方式保证线程安全

package sun.misc;

public final class Unsafe {
    //通过这个静态属性就可以获取到这个类的对象,只能通过反射来获取
    private static final Unsafe theUnsafe;
    
    //这三个方法是unsafe类提供的cas操作,是通过本地方法实现的
    //cas的意思是比较并设置值,参数一般会有一个期望旧值,将要设置的目标值,
    // 方法执行时会先判断变量的原始值是否和期望值一样,如果是就更新成目标值然后返回true,
    //如果变量的原始值和期望值不一样就返回false,
    //这个方法是通过本地方法(最终通过cpu指令)保证了上边这个比较并设置值的过程是原子操作
    /**
    * obj: 要修改那个对象的属性
    * offset: 对象属性相对于对象的内存偏移量
    * expect: 期望的旧值
    * newVal: 要设置成的目标值
    */
    public final native boolean compareAndSwapObject(Object obj, long offset, Object expect, Object newVal);

    public final native boolean compareAndSwapInt(Object obj, long offset, int expect, int newVal);

    public final native boolean compareAndSwapLong(Object obj, long offset, long expect, long newVal);
    
    //这是自增的方法,注意这个方法是先返回原来的值再自增的,类似i++这样的操作
    public final int getAndAddInt(Object obj, long offset, int step) {
        int var5;//这是对象属性的原始值
        do {
            //调用本类中直接操作内存的方法来获取对象obj的内存偏移量是offset的属性的最新值,
            //针对原子整数AtomicInteger就是获取value属性的最新值
            var5 = this.getIntVolatile(obj, offset);
            //这块就是调用cas方法设置对象属性的值,有可能成功或者失败,
            //如果失败就再次循环重新获取旧值进行更新的操作,直到更新成功退出循环
        } while(!this.compareAndSwapInt(obj, offset, var5, var5 + step));

        return var5;
    }
    
}

总结下getAndAddInt方法就是通过这样的cas操作+自旋的方式保证了外界调用的一次自增/自减操作一定是在最新值对象属性值的基础上进行的,这样就保证了这次操作的原子性,保证线程安全。

四、尝试使用Unsafe保证线程安全

这一节我们尝试下直接使用Unsaft类中的cas操作来保证自增/自减操作的原子性。

首先创建一个计数器类

public class Counter {
    // volatile保证多线程可见行
    public volatile int count;
    
    public Counter(int count) {
        this.count = count;
    }
}

测试类

@Slf4j
public class ThreadTest2 {

    //使用Counter类作为计数器
    static Counter counter = new Counter(0);

    public static void main(String[] args) throws InterruptedException, NoSuchFieldException, IllegalAccessException {

        //利用反射获取Unsafe对象
        Field theUnsafe = Unsafe.class.getDeclaredField("theUnsafe");
        theUnsafe.setAccessible(true);
        Unsafe unsafe = (Unsafe) theUnsafe.get(null);//静态变量获取时传null
        //获取Counter中count属性的内存偏移量
        long offset = unsafe.objectFieldOffset(Counter.class.getDeclaredField("count"));

        for (int k = 0; k < 100; k++) {
            Thread t1 = new Thread(new Runnable() {
                @Override
                public void run() {
                    for (int i = 0; i < 10; i++) {
                        //自己实现cas+自旋的操作
                        boolean res=false;
                        while (!res) {
                            //获取旧值
                            int old = unsafe.getIntVolatile(counter,offset);
                            //cas设置新值,得到cas操作结果
                            res = unsafe.compareAndSwapInt(counter,offset,old,old+1);
                        }
                        try {
                            Thread.sleep(1);
                        } catch (InterruptedException e) {
                            e.printStackTrace();
                        }
                    }
                }
            });

            Thread t2 = new Thread(new Runnable() {
                @Override
                public void run() {
                    for (int i = 0; i < 10; i++) {
                        //自己实现cas+自旋的操作
                        boolean res=false;
                        while (!res) {
                            //获取旧值
                            int old = unsafe.getIntVolatile(counter,offset);
                            //cas设置新值,得到cas操作结果
                            res = unsafe.compareAndSwapInt(counter,offset,old,old-1);
                        }
                        try {
                            Thread.sleep(2);
                        } catch (InterruptedException e) {
                            e.printStackTrace();
                        }
                    }
                }
            });

            t1.start();
            t2.start();
            t1.join();
            t2.join();

            log.info("res:{}", counter.count);
        }
    }
}

这段代码中关键是使用cas操作实现自增和自减

标签:count,Thread,int,Unsafe,AtomicInteger,cas,源码,public
From: https://www.cnblogs.com/chengxuxiaoyuan/p/17749623.html

相关文章

  • java代码注释和空行删除,软著源码
    notepad++替换,结合正则表达式处理匹配//单行注释//.*匹配/**多行注释*/注意需要非贪婪模式,所以有一个?使用/\*{2}[\s\S]*?\*/匹配空行^\s*\n上面多行注释的匹配,需要非贪婪模式如果没有那个问号,将会把这一整块都匹配进去,第一块多行注释的头/**和最后一个多行注释......
  • 视频直播源码,标题居中,底部按钮为三个时居中布局
    视频直播源码,标题居中,底部按钮为三个时居中布局更改底部按钮默认大写的设置 <stylename="CustomAlertDialog"parent="@style/Theme.AppCompat.Light.Dialog.Alert">    <itemname="buttonBarButtonStyle">@style/CustomAlertDialogButton</item><......
  • 直播平台源码,FlinkSQL实现行转列
    直播平台源码,FlinkSQL实现行转列1、使用UNNEST解析 select name,course,scorefromods_kafka_student_scores CROSSJOINUNNEST(`list`)ASt(course,score);select name,course,scorefromods_kafka_student_scores,UNNEST(`list`)ASt(course,score);select name......
  • linux内核升级和内核源码编译
    一、ubuntu通过命令安装内核版本1、检查原系统内核版本uname-r2、搜索可用linux内核版本apt-cachesearchlinux|greplinux-headers3、通过apt命令安装内核apt-getinstall linux-headers-5.4.0-80-generic linux-image-5.4.0-80-generic4、安装成功后查看/boot目录......
  • 基于android的中医体质的社区居民健康管理系统-计算机毕业设计源码+LW文档
    摘要首先,论文一开始便是清楚的论述了系统的研究内容。其次,剖析系统需求分析,弄明白“做什么”,分析包括业务分析和业务流程的分析以及用例分析,更进一步明确系统的需求。然后在明白了系统的需求基础上需要进一步地设计系统,主要包罗软件架构模式、整体功能模块、数据库设计。本......
  • 基于springboot的小程序的高校后勤管理系统-计算机毕业设计源码+LW文档
    1、选题背景与意义(含国内外相关研究综述及评价)近年来,随着计算机的不断发展和深入到各个行业中并起到了很重要的作用,给人们带来了很大的便利。在这样的趋势下,高校的后勤管理显得也很重要。在《高校后勤管理系统的设计与实现》中也提到,教育的普及和日益激烈的资源竞争,对学校的教学质......
  • 外卖小程序源码的安全性和隐私考虑
    外卖小程序源码的使用正在成为数字餐饮业的主流选择之一。然而,随着外卖业务的增长,安全性和隐私保护变得至关重要。在本文中,我们将探讨外卖小程序源码的安全性和隐私问题,并提供一些代码示例,以帮助开发者确保其应用程序的安全性和用户隐私。安全性考虑1.数据传输加密在外卖小程序中......
  • 泰到位小程序开发/APP源码案例演示
    泰到位盈利模式Thisarticleisonlyforsystemdevelopmentrequirementsreference平台收入:用户收入+门店订单佣金平台支出:营销成本+获客成本+人员工资成本 综合来看,对App提出以下几条优化建议: 1技师信息模块 疫情当下,作为上门服务的行业,有必要在技师信息列表中加入每日体......
  • linux 内核源码
    linux官网:https://www.kernel.org/1、第一列为版本描述:主线、稳定版、长期支持版;第二列为内核版本号;第三列为最后更新时间;2、tarball:完整的代码;pgp:验证签名;patch:基于上一个版本的补丁(一般商用的系统不会频繁的编译内核,所以可以打补丁上去方便)3、点击[tarball]下载完成版代......
  • vscode单步调试Android c++源码
    vscode单步调试Androidc++源码  目录步骤1.运行gdbclient.py脚本2.复制生成的launch.json并新建/home/jetson/android_aosp/aosp/.vscode/launch.json3.运行gdb即可,打断点参考 步骤注意:这个过程需要在Android源码环境中运行,可以使用adb端口转发工具,来......