当前位置: 首页 > news >正文

并发之AtomicInteger

并发之AtomicInteger
1 java.util.concurrent.atomic概要
    在java.util.concurrent.atomic包下存在着18个类,其中Integer、Long、Reference、各占三个,boolean占据一个,Double各Long的accumulator和add各占两个。为了解决CAS的ABA问题的类库占据两个,一个包类以及一个剩余的Striped64类,接下来我们从第一个类开始进行源码的解析工作;

 

1 AtomicInteger解析
    众所周知,在多线程并发的情况下,对于成员变量,是线程不安全的;一个很简单的例子,假设我存在两个线程,让一个整数自增1000次,那么最终的值应该是1000;但是多线程情况下并不能保证原子性;最终的结果极有可能鄙视1000;看如下的代码:
package automic;
 
public class AtomicIntegerTest extends Thread{
 
    private Integer count=0;
    @Override
    public void run() {
        for(int i=1;i<=500;i++){
            count++;
        }
         System.out.println("count的值是:"+ count);
 
    }
 
    public static void main(String[] args) {
        AtomicIntegerTest a=new AtomicIntegerTest();
            Thread t1 = new Thread(a);
            Thread t2 = new Thread(a);
            t1.start();
            t2.start();
           
    }
 
}
最终的结果无论如何都是小于1000的,这个我们可以很好的理解,这是因为两个线程可能同时去修改了变量的值导致的;在这里我们还是不讨论Java中的锁的技术,因为我这里主要为了阐述Java中无锁的技术;CAS算法是基于乐观锁的实现方法,在不需要锁的情况下,并且在并发量不高的情况下完成的原子性的操作;主要原理是当一个线程去修改这个值得时候会进入一个while循环,并且不断的尝试comparreAndSet()方法,当值修改完毕结束死循环;关于具体的CAS的算法的问题,在这里我不做过多的赘述;
对于上述的代码,我们可以将Integer修改为AutomicInteger,且看如下的代码:
package automic;
 
import java.util.concurrent.atomic.AtomicInteger;
 
public 
class AtomicIntegerTest2 extends Thread{
    /**
     * 这里使用了AtomicInteger类,这是一个对于变量可以进行原子性操作的类;核心是CAS无锁算法;
     * 下面两个构造器其中一个进行了值得初始化
     * public AtomicInteger(int initialValue) {
     *   value = initialValue;
     * }
     * public AtomicInteger() {
     * }
     */
 
    
    private AtomicInteger count=new AtomicInteger(0);
    @Override
    public void run() {
        for(int i=1;i<=500;i++){
            /**
             * getAndIncrement是以原子的方式给当前值加1
             */
            count.getAndIncrement();
        }
         System.out.println("count的值是:"+ count);
 
    }
 
    public static void main(String[] args) {
        AtomicIntegerTest2 a=new AtomicIntegerTest2();
            Thread t1 = new Thread(a);
            Thread t2 = new Thread(a);
            t1.start();
            t2.start();
           
    }
 
}
最终的结果是1000;大家看到了吗,我们采用了AtomicInteger可以保证数据的原子性操作,多线程并发的情况下是安全的;
    对于线程的安全来说,是一个老生常谈的问题:做到线程安全,我们最直接的方法一般有两种:
    1> 同步锁或者同步代码块
    2> 互斥锁或者重入锁
其实上述的两种都是利用锁的方式来解决线程并发问题,而且都是悲观锁的方式;
synchrnoized在jdk1.6之后做了优化,在性能上和Lock锁处于相同的数量级的位置上;好像又扯开了话题:
    synchnoized本身就具备了原子性操作;即锁的方式本身就保证了数据操作的原子性;而CAS算法没有利用锁的技术;他如何实现的呢?
且看如下的源代码:
public class AtomicInteger extends Number implements java.io.Serializable {
    private static final long serialVersionUID = 6214790243416807050L;
 
    // setup to use Unsafe.compareAndSwapInt for updates
    private static final Unsafe unsafe = Unsafe.getUnsafe();
    private static final long valueOffset;
}

 

① Unsafe是CAS的核心类,一切底层的具体实现由他来完成;
② valueOffset 变量在内存中地址的起始偏移量;
如下的静态代码块是完成变量的初始化;当JVM加载该类的时候就为这个变量在内存中开辟内存地址;它通过反射的手法获取字段value的值,而value的值使用了volatile去修饰,保证了内存的可见性(这点至关重要);但是volatile本身不可以保证操作的原子性;
 static {
        try {
            valueOffset = unsafe.objectFieldOffset
                (AtomicInteger.class.getDeclaredField("value"));
        } catch (Exception ex) { throw new Error(ex); }
    }
private volatile int value;
 
再来看一看这个方法getAndIncrement();表示给特定的变量添加1;这个方法的源码如下:为了明晰原理,我这里使用的是jdk1.7
 public final int getAndIncrement() {
        for (;;) {
            int current = get();
            int next = current + 1;
            if (compareAndSet(current, next))
                return current;
        }
    }

在看看jdk1.8的源码:

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

使用了unsafe的方法,其实二者底层的实现方式都差不多;进入一个for循环,不断的比较内存值和期望值,如果相等就修改,不相等就返回false;

    /**
     * set()方法 ,设置一个值
     */
    public final void set(int newValue) {
        value = newValue;
    }
 
    /**
     * lazySet()方法,没有storeload屏障的set,出现于JDK1.6
     */
    public final void lazySet(int newValue) {
        unsafe.putOrderedInt(this, valueOffset, newValue);
    }
 
    /**
     * getAndSet()方法 原子性的获取并且设置值
     */
    public final int getAndSet(int newValue) {
        return unsafe.getAndSetInt(this, valueOffset, newValue);
    }
 
    /**
     * 如果当前值和内存值相等,那么进行更新
     */
    public final boolean compareAndSet(int expect, int update) {
        return unsafe.compareAndSwapInt(this, valueOffset, expect, update);
    }
 
    /**
     * weak的CAS,也就是没有volatile语义的CAS,没有加入内存屏障
     */
    public final boolean weakCompareAndSet(int expect, int update) {
        return unsafe.compareAndSwapInt(this, valueOffset, expect, update);
    }
 
    /**
     * 自增加,返回原来的值.
     */
    public final int getAndIncrement() {
        return unsafe.getAndAddInt(this, valueOffset, 1);
    }
 
    /**
     * 自减少,返回原来的值
     */
    public final int getAndDecrement() {
        return unsafe.getAndAddInt(this, valueOffset, -1);
    }
 
    /**
     * 原子性的增加delta的值
     */
    public final int getAndAdd(int delta) {
        return unsafe.getAndAddInt(this, valueOffset, delta);
    }
 
    /**
     * 自增1
     */
    public final int incrementAndGet() {
        return unsafe.getAndAddInt(this, valueOffset, 1) + 1;
    }
 
    /**
     * 自减1
     */
    public final int decrementAndGet() {
        return unsafe.getAndAddInt(this, valueOffset, -1) - 1;
    }
 
   
    /**
     * 阻塞式更新,并且对prev进行一个IntUnaryOperator操作运算
     */
    public final int updateAndGet(IntUnaryOperator updateFunction) {
        int prev, next;
        do {
            prev = get();
            next = updateFunction.applyAsInt(prev);
        } while (!compareAndSet(prev, next));
        return next;
    }
 
    /**
     * 阻塞式更新,并对prev和x,进行二元运算操作。于jdk1.8出现
     */
    public final int getAndAccumulate(int x,
                                      IntBinaryOperator accumulatorFunction) {
        int prev, next;
        do {
            prev = get();
            next = accumulatorFunction.applyAsInt(prev, x);
        } while (!compareAndSet(prev, next));
        return prev;
    }

上述的一些方法的解释:

    lazySet():最后设置为给定值。这个方法和Set方法的基本功能是一样的,但是set()方法的写入是将新的值赋值给给value,而value是volatile修饰的如果内存中的变量发生了修改,那么会对所有的线程立即可见;之所以可以立即可见,这是因为volatile会对线程追加两个内存屏障
   1)StoreStore // 在intel cpu中, 不存在[写写]重排序, 这个可以直接 省略了 
    2)StoreLoad // 这个是所有内存屏障里最耗性能的
但是1)中的 StroeStroe效率太低,因此lazySet抛弃了第一个内存屏障,只有StoreLoad,所以它的写不一定会被其他线程立即可见;
weakCompareAndSet()这个方法和compareAndSet()方法的实现原理是一致的,但是是一个弱的CAS算法,没有valatile的CAS;在读取上是原子性的,但是在写的时候不具有valatile语义啦!
 
 
 
 

转载于:https://www.cnblogs.com/gosaint/p/9046867.html

相关文章:

  • CentOS6.5使用createrepo搭建本地源
  • Docker系列教程21-Docker Compose快速入门
  • centOS下NFS服务器的安装配置详解
  • IDEA下从零开始搭建SpringBoot工程
  • SQL SERVER时间格式化
  • top命令的使用
  • 手机WAP前端开发标准
  • 微信钱包入口开发笔记
  • 编程是一个没有前途的工作
  • 32位系统(软件)和64位系统(软件)的区别(跑的快,内存支持多)
  • SQL点滴34—SQL中的大小写
  • Windows 10下安装配置Caffe并支持GPU加速(修改版)
  • mac homebrew安装
  • 每天10道编程题-第四天
  • 【连载】物联网全栈教程-从云端到设备(十二)---最简单的单片机上云方法!...
  • 【译】理解JavaScript:new 关键字
  • 2017前端实习生面试总结
  • 2018以太坊智能合约编程语言solidity的最佳IDEs
  • 2019.2.20 c++ 知识梳理
  • android 一些 utils
  • crontab执行失败的多种原因
  • css布局,左右固定中间自适应实现
  • CSS实用技巧干货
  • es的写入过程
  • JavaScript 事件——“事件类型”中“HTML5事件”的注意要点
  • mysql innodb 索引使用指南
  • nodejs调试方法
  • Python_网络编程
  • React Native移动开发实战-3-实现页面间的数据传递
  • Xmanager 远程桌面 CentOS 7
  • Zsh 开发指南(第十四篇 文件读写)
  • 编写符合Python风格的对象
  • 高度不固定时垂直居中
  • 数据结构java版之冒泡排序及优化
  • 我的zsh配置, 2019最新方案
  • 学习使用ExpressJS 4.0中的新Router
  • mysql 慢查询分析工具:pt-query-digest 在mac 上的安装使用 ...
  • ​力扣解法汇总1802. 有界数组中指定下标处的最大值
  • #162 (Div. 2)
  • #传输# #传输数据判断#
  • #数学建模# 线性规划问题的Matlab求解
  • #我与Java虚拟机的故事#连载05:Java虚拟机的修炼之道
  • #我与Java虚拟机的故事#连载13:有这本书就够了
  • (react踩过的坑)antd 如何同时获取一个select 的value和 label值
  • (Redis使用系列) Springboot 整合Redisson 实现分布式锁 七
  • (附源码)springboot宠物医疗服务网站 毕业设计688413
  • (附源码)ssm基于微信小程序的疫苗管理系统 毕业设计 092354
  • (三) diretfbrc详解
  • .NET Core 通过 Ef Core 操作 Mysql
  • .NET Core、DNX、DNU、DNVM、MVC6学习资料
  • .Net 应用中使用dot trace进行性能诊断
  • .NET/C# 判断某个类是否是泛型类型或泛型接口的子类型
  • .Net小白的大学四年,内含面经
  • @property python知乎_Python3基础之:property
  • [ C++ ] STL---仿函数与priority_queue