(基础系列)atomic原子变量类的用法、原理和用途

前言

在当今科技高速发展的时代,计算机的高速发展早已超越“摩尔定律”。在这个计算机相对廉价的时代,做为开发者操做的机器早已不是单核处理器了,早已进入多核时代,业务早已进入并行执行;开发高并发的程序所要掌握的技能也再也不是使用没有效率的锁,所幸jdk1.5提供在多线程状况下无锁的进行原子操做,也就是这篇文章将要写的内容。java

什么是“原子变量类”?

自JDK1.5开始提供了java.util.concurrent.atomic包,方便程序员在多线程环境下,无锁的进行原子操做。原子变量的底层使用了处理器提供的原子指令,可是不一样的CPU架构可能提供的原子指令不同,也有可能须要某种形式的内部锁,因此该方法不能绝对保证线程不被阻塞。- 总的来讲就是提供非阻塞的线程安全编程node

原子变量类的简单用法

在介绍用法前先了解jdk软件包 java.util.concurrent.atomic 中为咱们提供了哪些原子类和方法: 程序员

(1)类摘要 编程

描述
AtomicBoolean 能够用原子方式更新的 boolean 值。
AtomicInteger 能够用原子方式更新的 int 值。
AtomicIntegerArray 能够用原子方式更新其元素的 int 数组。
AtomicIntegerFieldUpdater 基于反射的实用工具,能够对指定类的指定 volatile int 字段进行原子更新。
AtomicLong 能够用原子方式更新的 long 值。
AtomicLongArray 能够用原子方式更新其元素的 long 数组。
AtomicLongFieldUpdater 基于反射的实用工具,能够对指定类的指定 volatile long 字段进行原子更新。
AtomicMarkableReference AtomicMarkableReference 维护带有标记位的对象引用,能够原子方式对其进行更新。
AtomicReference 能够用原子方式更新的对象引用。
AtomicReferenceArray 能够用原子方式更新其元素的对象引用数组。
AtomicReferenceFieldUpdater 基于反射的实用工具,能够对指定类的指定 volatile 字段进行原子更新。
AtomicStampedReference AtomicStampedReference 维护带有整数“标志”的对象引用,能够用原子方式对其进行更新。

(2)经常使用方法摘要api

返回类型 方法 描述
boolean compareAndSet(boolean expect, boolean update) 若是当前值 == 预期值,则以原子方式将该值设置为给定的更新值
boolean get() 返回当前值。
void set(boolean newValue) 无条件地设置为给定值。
boolean weakCompareAndSet(boolean expect, boolean update) 若是当前值 == 预期值,则以原子方式将该值设置为给定的更新值。

这里介绍只列出经常使用的方法,实际中据原子类不一样方法略有变化。如需了解更多的方法请查看“在线文档-jdk-z”数组

在线文档-jdk-zh安全

(3)简单使用示例 bash

示例一:原子更新基本类型类--生成序列号数据结构

public class Example1 {

    private final AtomicLong sequenceNumber = new AtomicLong(0);
    public long next() {
        //原子增量方法,执行的是i++,因此须要在获取一次。
        sequenceNumber.getAndIncrement();
        return sequenceNumber.get();
    }

    public void radixNext(int radix){
        for (;;) {
            long i = sequenceNumber.get();
            // 该方法不必定执行成功,因此用无限循环来保证操做始终会执行成功一次。
            boolean suc = sequenceNumber.compareAndSet(i, i + radix);
            if (suc) {
                break;
            }
        }
    }


    public static void main(String[] args) {
        Example1 sequencer = new Example1();

        //生成序列号
        for (int i = 0; i < 10; i++) {
            System.out.println(sequencer.next());
        }

        //生成自定义序列号
        for (int i = 0; i < 10; i++) {
            sequencer.radixNext(3);
            System.out.println(sequencer.sequenceNumber.get());
        }


    }

}复制代码

执行结果:多线程

1
2
3
4
5
---------------
8
11
14
17
20复制代码

示例二:原子方式更新数组

public class Example2 {

    static AtomicIntegerArray arr = new AtomicIntegerArray(10);

    public static class AddThread implements Runnable{
        public void run(){
            for(int k=0;k<10000;k++){
                // 以原子方式将索引 i 的元素加 1。
                arr.getAndIncrement(k%arr.length());
            }

        }
    }

    public static void main(String[] args) throws InterruptedException {
        Thread[]ts=new Thread[10];
        //建立10个线程
        for(int k=0;k<10;k++){
            ts[k] = new Thread(new AddThread());
        }

        //开启10个线程
        for(int k=0;k<10;k++){
            ts[k].start();
        }

        //等待全部线程执行完成
        for(int k=0;k<10;k++){
            ts[k].join();
        }

        //打印最终执行结果
        System.out.println(arr);
    }
}复制代码

执行结果:

[10000, 10000, 10000, 10000, 10000, 10000, 10000, 10000, 10000, 10000]复制代码

示例三:原子方式更新引用

public class Node {
    private int val;
    private volatile Node left, right;

    private static final AtomicReferenceFieldUpdater leftUpdater = AtomicReferenceFieldUpdater.newUpdater(Node.class, Node.class, "left");
    private static AtomicReferenceFieldUpdater rightUpdater = AtomicReferenceFieldUpdater.newUpdater(Node.class, Node.class, "right");

    boolean compareAndSetLeft(Node expect, Node update) {
        return leftUpdater.compareAndSet(this, expect, update);
    }

    public Node() {
        this.left = this.right = null;
    }

    public Node(int val) {
        this.val = val;
        this.left = this.right = null;
    }

    public Node(Node left,Node right) {
        this.left = left;
        this.right = right;
    }


    public static void main(String[] args) {
        Node node = new Node(1);
        node.left = new Node(new Node(2),new Node(3));
        node.right = new Node(new Node(4),new Node(5));
        System.out.println(JSON.toJSON(node));
        node.compareAndSetLeft(node.left,node.right);
        System.out.println(JSON.toJSON(node));
    }



    // get and set ...

}复制代码

执行结果:

{"val":1,"left":{"val":0,"left":{"val":2},"right":{"val":3}},"right":{"val":0,"left":{"val":4},"right":{"val":5}}}
{"val":1,"left":{"val":0,"left":{"val":4},"right":{"val":5}},"right":{"val":0,"left":{"val":4},"right":{"val":5}}}复制代码

(4)小结

原子访问和更新的内存效果通常遵循如下可变规则中的声明:

  • get 具备读取 volatile 变量的内存效果。
  • set 具备写入(分配)volatile 变量的内存效果。
    除了容许使用后续(但不是之前的)内存操做,其自身不施加带有普通的非 volatile 写入的从新排序约束,lazySet 具备写入(分配)volatile 变量的内存效果。在其余使用上下文中,当为 null 时(为了垃圾回收),lazySet 能够应用不会再次访问的引用。
  • weakCompareAndSet 以原子方式读取和有条件地写入变量但不 建立任何 happen-before 排序,所以不提供与除 - weakCompareAndSet 目标外任何变量之前或后续读取或写入操做有关的任何保证。
  • compareAndSet 和全部其余的读取和更新操做(如 getAndIncrement)都有读取和写入 volatile 变量的内存效果。

原子操做的实现原理

关键源码:

// setup to use Unsafe.compareAndSwapInt for updates
private static final Unsafe unsafe = Unsafe.getUnsafe();复制代码

查看源码发现Atomic包里的类基本都是使用Unsafe实现的,Unsafe只提供了如下三种CAS方法。

关键源码:

public final native boolean compareAndSwapObject(Object var1, long var2, Object var4, Object var5);

public final native boolean compareAndSwapInt(Object var1, long var2, int var4, int var5);

public final native boolean compareAndSwapLong(Object var1, long var2, long var4, long var6);复制代码

查看方法不难发现是被native修饰的,即被Native修饰的方法在被调用时指向的是一个非java代码的具体实现,这个实现多是其余语言或者操做系统。这里是借助C来调用CPU底层指令来实现的,具体实现原理请点击下面的“实现原理”。

实现原理

原子对象的用途

原子变量类主要用做各类构造块,用于实现非阻塞数据结构和相关的基础结构类。compareAndSet方法不是锁的常规替换方法。仅当对象的重要更新限定于单个变量时才应用它。

例:多线程高并发计数器

总结 (摘自网上)

原子变量类相对于基于锁的版本有几个性能优点。首先,它用硬件的原生形态代替 JVM 的锁定代码路径,从而在更细的粒度层次上(独立的内存位置)进行同步,失败的线程也能够当即重试,而不会被挂起后从新调度。更细的粒度下降了争用的机会,不用从新调度就能重试的能力也下降了争用的成本。即便有少许失败的 CAS 操做,这种方法仍然会比因为锁争用形成的从新调度快得多。

相关文章
相关标签/搜索