阅读 2543

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

前言

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

什么是“原子变量类”?

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

原子变量类的简单用法

在介绍用法前先了解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)常用方法摘要

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

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

在线文档-jdk-zh

(3)简单使用示例

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

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 操作,这种方法仍然会比由于锁争用造成的重新调度快得多。

文章分类
后端