阅读 852

Java 多线程 : 简简单单原子类

前言

原子类继承于 Number 接口 , 归属于java.util.concurrent.atomic 包中 , 原子类的主要作用是为了解决 synchronized 处理原子操作 资源消耗过高 的问题 ,原子类的CAS 基于 Unsafe 实现 .

一. 原子类的简述

1.1 原子类的应用场景

原子类适用于需要原子操作而有需要减少资源消耗时 , 原子类相当于 volatile 和 CAS 的工具类 .

1.2 原子类的类型

基本类型 : 使用原子的方式更新基本类型

  • AtomicInteger:整形原子类
  • AtomicLong:长整型原子类
  • AtomicBoolean :布尔型原子类

数组类型 : 使用原子的方式更新数组里的某个元素

  • AtomicIntegerArray:整形数组原子类
  • AtomicLongArray:长整形数组原子类
  • AtomicReferenceArray :引用类型数组原子类

引用类型

  • AtomicReference:引用类型原子类
  • AtomicStampedRerence:原子更新引用类型里的字段原子类
  • AtomicMarkableReference :原子更新带有标记位的引用类型

对象的属性修改类型

  • AtomicIntegerFieldUpdater:原子更新整形字段的更新器
  • AtomicLongFieldUpdater:原子更新长整形字段的更新器
  • AtomicStampedReference :原子更新带有版本号的引用类型。
    • 该类将整数值与引用关联起来
    • 可用于解决原子的更新数据和数据的版本号
    • 可以解决使用 CAS 进行原子更新时可能出现的 ABA 问题。

1.3 . AtomicInteger 类常用方法

  • public final int get() : 获取当前的值java
  • public final int getAndSet(int newValue) : 获取当前的值,并设置新的值
  • public final int getAndIncrement(): 获取当前的值,并自增
  • public final int getAndDecrement() : 获取当前的值,并自减
  • public final int getAndAdd(int delta) : 获取当前的值,并加上预期的值
  • boolean compareAndSet(int expect, int update) : 如果输入的数值等于预期值,则以原子方式将该值设置为输入值(update)
  • public final void lazySet(int newValue) : 最终设置为newValue,使用 lazySet 设置之后可能导致其他线程在之后的一小段时间内还是可以读到旧的值。
  • public final int incrementAndGet() : 以原子方式给当前值加1并获取新值
  • public final int decrementAndGet() : 以原子方式给当前值减1并获取新值
  • public final int addAndGet(int delta) : 以原子方式给当前值加delta并获取新值
  • public final boolean compareAndSet(int expect, int update) : CAS 比较方法

二 . 原子类的原理

2.1 原理简述

原子操作是指不会被线程调度机制打断的操作;这种操作一旦开始,就一直运行到结束,中间不会有任何 contextswitch切换到另一个线程 , 之所以称为原子变量,是因为其包含一些以原子方式实现组合操作的方法

回顾 CAS : CAS的原理是拿期望的值和原本的一个值作比较,如果相同则更新成新的值

原子类主要通过 CAS (compare and swap) + volatile 和 native 方法来保证原子操作 !

// 案例 : AtomicInteger

// 它的主要内部成员是:
private volatile int value;
注意,它的声明带有volatile,这是必需的,以保证内存可见性。

// 它的大部分更新方法实现都类似,我们看一个方法incrementAndGet,其代码为:
public final int incrementAndGet() {
    for (;;) {
        int current = get();
        int next = current + 1;
        if (compareAndSet(current, next))
            return next;
    }
}

// 重点 : 
1 . value 是一个volatile变量,在内存中可见,因此 JVM 可以保证任何时刻任何线程总能拿到该变量的最新值
2 . UnSafe 类的objectFieldOffset()方法是一个本地方法,这个方法是用来拿到“原来的值”的内存地址 返回值是 valueOffset


复制代码
  1. 先获取当前值current,计算期望的值next
  2. 然后调用CAS方法进行更新,如果当前值没有变,则更新并返回新值,否则继续循环直到更新成功为止

2.2 AtomicInteger 原理深入

我们从源码看看那些之前被我们忽略的东西 , 此类可以代表大多数基本类型


// Node 1 : 原子类支持序列化
implements java.io.Serializable

----------------------> 

// Node 2 : CAS 对象 Unsafe , Unsafe 之前已经说过了, 其中有很多 Native 犯法
 private static final Unsafe unsafe = Unsafe.getUnsafe();

----------------------> 

// Node 3 : 偏移量 valueOffset
private static final long valueOffset;
static {
    try {
        valueOffset = unsafe.objectFieldOffset(AtomicInteger.class.getDeclaredField("value"));
    } catch (Exception ex) { throw new Error(ex); }
}
// objectFieldOffset() : 获取某个字段相对Java对象的“起始地址”的偏移量 , 后续通过便宜量获取方法
// getDeclaredField() : 返回一个字段对象,该对象反映由这个类对象表示的类或接口的指定声明字段

----------------------> 

// Node 4 : 核心值 Value  ,可以看到 value 使用 volatile 进行修饰
private volatile int value;

----------------------> 

// Node 5 : 操作方法 , 可以看到 valueOffset 此时已经发挥了作用
 public final int incrementAndGet() {
        return unsafe.getAndAddInt(this, valueOffset, 1) + 1;
}


----------------------> 
// Node 6 : 值转换 , AtomicInteger 提供了以下四个值得固有转换方法
public int intValue() ;
public long longValue() ;
public float floatValue();
public double doubleValue();

复制代码

2.3 AtomicReference 深入

现在看一下 AtomicReference 有什么特别之处


// Node 1 : 不再继承 Number 接口

// Node 2 : 使用泛型方法
public class AtomicReference<V> implements java.io.Serializable
private volatile V value;

// Node 3 : 比对时使用 putOrderedObject
return unsafe.compareAndSwapObject(this, valueOffset, expect, update);

复制代码

2.4 AtomicIntegerArray 深入

相对而言 AtomicIntegerArray 有更多得变化 , 其他的大同小异


// Node 1 : 对数组元素偏移进行了记录 , 此处不再是 "value" 偏移
private static final int base = unsafe.arrayBaseOffset(int[].class);
private static final int shift;
private final int[] array;

// Node 2 : 比较使用了 getAndSetInt
unsafe.getAndSetInt(array, checkedByteOffset(i), newValue)

复制代码

三 . 原子类的操作

3.1 原子类常见案例

AtomicInteger integer = new AtomicInteger();

logger.info("------> 1 > 获取原子变量 :[{}] <-------", integer.get());

// Step 2 : 设置参数
integer.set(999);

logger.info("------> 2 > 获取原子变量 :[{}] <-------", integer.get());


logger.info("------> 失败比较获取 : 测试比较判断 :[{}] <-------", integer.compareAndSet(0, 998));
logger.info("------> 3 > 获取原子变量 :[{}] <-------", integer.get());

logger.info("------> 成功比较获取 : 测试比较判断 :[{}] <-------", integer.compareAndSet(999, 998));
logger.info("------> 4 > 获取原子变量 :[{}] <-------", integer.get());


// Step 3 : 获取当前的值,并设置新的值
logger.info("------> 测试比较判断 :[{}] <-------", integer.getAndSet(888));
logger.info("------> 5 > 获取原子变量 :[{}] <-------", integer.get());


// Step 4 : 获取当前的值,并设置新的值
logger.info("------> 测试比较判断 :[{}] <-------", integer.getAndIncrement());
logger.info("------> 6 > 获取原子变量 :[{}] <-------", integer.get());

// 以原子方式给当前值加1并获取新值
logger.info("------> 测试比较判断 :[{}] <-------", integer.incrementAndGet());
logger.info("------> 6-1 > 获取原子变量 :[{}] <-------", integer.get());

// Step 5 : 获取当前的值,并设置新的值
logger.info("------> 测试比较判断 :[{}] <-------", integer.getAndDecrement());
logger.info("------> 7 > 获取原子变量 :[{}] <-------", integer.get());

// 以原子方式给当前值减1并获取新值
logger.info("------> 测试比较判断 :[{}] <-------", integer.decrementAndGet());
logger.info("------> 7 > 获取原子变量 :[{}] <-------", integer.get());

// Step 6 : 获取当前的值,并设置新的值
logger.info("------> 测试比较判断 :[{}] <-------", integer.getAndAdd(99));
logger.info("------> 8 > 获取原子变量 :[{}] <-------", integer.get());

// 以原子方式给当前值加delta并获取新值
logger.info("------> 测试比较判断 :[{}] <-------", integer.addAndGet(99));
logger.info("------> 8 > 获取原子变量 :[{}] <-------", integer.get());
}

复制代码

3.2 原子类测试多线程情况

 /**
     * 测多线程方式
     */
    public void testThead() throws Exception {
    
        InnerTO innerTO = new InnerTO();
        MyThread[] threadDSS = new MyThread[1000];

        for (int i = 0; i < 1000; i++) {
            threadDSS[i] = new MyThread(innerTO);
        }
        for (int i = 0; i < 1000; i++) {
            threadDSS[i].start();
        }

        logger.info("------> 原子类线程 Start 完成 :{} <-------", innerTO.getInteger().get());
        for (int i = 0; i < 1000; i++) {
            if (i % 100 == 0) {
                Thread.sleep(1);
                logger.info("------> 测试原子类 :{} <-------", innerTO.getInteger().get());
            }
        }
    }

    /**
    * 包含原子类的对象
    *
    **/
    class InnerTO {
        AtomicInteger integer = new AtomicInteger();
        public AtomicInteger getInteger() {
            return integer;
        }
        
        public void setInteger(AtomicInteger integer) {
            this.integer = integer;
        }
    }

    /**
    * 运行线程类
    *
    **/
    class MyThread extends Thread {

        public InnerTO innerTO = new InnerTO();

        public MyThread(InnerTO innerTO) {
            this.innerTO = innerTO;
        }

        @Override
        public void run() {
            int i = innerTO.getInteger().getAndIncrement();
            if (i == 999) {
                logger.info("------> 线程执行完成 <-------");
            }
        }
    }
    
// 可以看到在没有锁的情况下 ,数据保证了原子性
 ------> 原子类线程 Start 完成 :876 <-------
 ------> 测试原子类 :918 <-------
 ------> 测试原子类 :950 <-------
 ------> 测试原子类 :973 <-------
 ------> 测试原子类 :989 <-------
 ------> 线程执行完成 <-------
 ------> 测试原子类 :1000 <-------
 ------> 测试原子类 :1000 <-------
 ------> 测试原子类 :1000 <-------
 ------> 测试原子类 :1000 <-------
 ------> 测试原子类 :1000 <-------
 ------> 测试原子类 :1000 <-------    

复制代码

欢迎大家关注我的相关文档 多线程集合

参考文档

[芋道源码](http://www.iocoder.cn/JUC/sike/aqs-3/)

[死磕系列](http://cmsblogs.com/?cat=151)

复制代码
文章分类
后端
文章标签