AtomicXFieldUpdater,属性原子修改的外部工具类

栏目: Java · 发布时间: 6年前

内容简介:AtomicXFieldUpdater,属性原子修改的外部工具类

最近在看资料的时候偶然间看到了AtomicLongFieldUpdater这个 工具 类,觉得新鲜就查阅了相关的资料,发现居然是jdk1.5就有的工具类,不禁感叹自己对 Java 的理解还是太浅了,于是在此整理一下该类的资料,作为知识储备。

AtomicXFieldUpdater

根据名字,我们可以知道 AtomicLongFieldUpdater 是对long型field进行原子update的,它是一个工具类,那么有long型的话是不是就有其他类型?通过查看相应的包,发现还有 AtomicIntegerFieldUpdaterAtomicReferenceFieldUpdater 两个“同胞兄弟”,它们3个加起来,支持了对Integer、long、Reference的原子操作,分别对应的原子类为AtomicInteger、AtomicLong、AtomicReference,那么updater和对应的原子类有什么区别呢?

AtomicLongFieldUpdater 来说,通过API文档,其中写了一句:“通过反射技术来对volatile修饰的long型属性进行原子更新”。这里面有几个关键词: 反射volatile原子更新

本着 talk is cheap, show me the code 的原则,直接上代码来展示它的用法,代码来自 博客3

// LongTest.java的源码
import java.util.concurrent.atomic.AtomicLongFieldUpdater;

public class LongFieldTest{

    public static void main(String[] args){

        // 获取Person的class对象
        Class cls = Person.class;
        // 新建AtomicLongFieldUpdater对象,传递参数是“class对象”和要update的属性名"id"
        AtomicLongFieldUpdater mAtoLong = AtomicLongFieldUpdater.newUpdater(cls, "id");
        Person person = new Person(12345678L);

        // 比较person的"id"属性,如果id的值为12345678L,则设置为1000。
        mAtoLong.compareAndSet(person, 12345678L, 1000);
        System.out.println("id="+person.getId());
    }
}

class Person{
    volatile long id;
    public Person(long id){
        this.id = id;
    }
    public void setId(long id){
        this.id = id;
    }
    public long getId(){
        return id;
    }
}

上面代码最终输出的为1000,有几个需要关注的点

  1. updater通过 AtomicLongFieldUpdater.newUpdater 来构造,通过传入类对应的class和要修改的属性名”id”指定该updater所要修改的类的属性,此处为Person的id
  2. 通过compareAndSet(CAS)来进行原子的修改,12345678L是expectedValue,如果传入的person对象的id不为12345678L,则修改失败,这里保证了线程安全,试想在多线程环境下,threadA和threadB同时执行CAS操作,12345678L是传入的expectedValue,只有第一个到达的线程可以成功的执行CAS,将该id修改成希望修改的值

根据Java API的说法,这个工具类不能保证完全的原子性,它只能保证相同updater上执行CAS和set操作的原子性,因此它的原子性是弱与AtomicLong的。为什么呢?因为AtomicLong是对long型属性加了一层原子引用,任何想要修改该long值的操作都需要先获得该原子引用,而updater不会为属性增加原子引用,它是通过反射技术,通过外部操作去修改long型属性值,因此它的原子保证也是通过外部限制的,因此只能保证同一updater进行CAS和set的原子性。

可以说上面的特性既是 AtomicLongFieldUpdater 的优点,也是它的缺点,为什么说呢?试想对于下面的类(代码来源于 博客1 ),Record是一个记录类,保存了系统中的一条记录信息,version属性是版本的计数。

public class Record{
 private final AtomicLong version = new AtomicLong(0);

 public long update(){
   return version.incrementAndGet();
 }
}

update方法更新版本号使其加一。这个类看起来逻辑十分清晰,但有一个隐藏的缺陷,就是每个Record都有一个对应的AtomicLong与之对应,如果系统有上亿条Record( 夸张而谈 ),而我们对于update方法的调用并不是很多,更多的是读取version的值,那么这会对堆空间造成严重的污染,大量的AtomicLong存在于堆上( 栈上存引用,堆上存实际的对象值 ),造成了内存的浪费,且AtomicLong的读取需要使用get方法,效率比正常变量的读取要慢,那么如何优化呢?

使用 AtomicLongFieldUpdater ,我们可以把Record类定义如下:

public class Record{
 private static final AtomicLongFieldUpdater<Record> VERSION =
      AtomicLongFieldUpdater.newUpdater(Record.class, "version");

 private volatile long version = 0;

 public long update(){
   return VERSION.incrementAndGet(this);
 }
}

通过改造,对于version属性正常的读取操作可以像普通属性读取那样进行,而当需要update的时候,使用 AtomicLongFieldUpdaterincrementAndGet 方法来进行,这样内存空间节省了下来(1. AtomicLong对象的引用和值统统不需要,对于拥有大量Record的系统无疑是很大的内存空间 2. version的读取和普通属性读取相同),还有一个好处是 AtomicLongFieldUpdater 是一个静态常量,它在Record类加载的时候就放在了堆空间的常量池中,对于N个Record,只需要一个 AtomicLongFieldUpdater 即可(类静态常量),如何还不清晰的话,可以把static final修饰的看作一个”全局常量”,整个系统只存在一个。

因此,虽然原子性不如 AtomicLong ,但它的效率很高,在特定的场景下有着很好的应用。Stack Overflow有一个回答比较好 引文3 ,对于CPU的消耗来说,其从小到大依次为:

  • long : 最小, 多线程不安全
  • volatile long : 消耗>long, 多线程读取安全,但无法进行原子操作
  • AtomicLong :消耗>volatile long,多线程读安全,可进行原子操作
  • AtomicLongFieldUpdate :消耗>AtomicLong,因为其使用反射技术,多线程安全和可原子操作

因此 AtomicLongFieldUpdate 的场景是,对于long的正常读写是占比很大的操作,原子操作只占很小的比例,并且多线程读取时必要的,此时可以使用 AtomicLongFieldUpdate + volatile 的组合。

AtomicReference

如果我们有一个双向链表,想要对链表中节点的替换进行原子操作,此时我们可以使用如下的代码:

class Node{
   private volatile Node left, right;

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

   NodegetLeft(){ return left; }
   boolean compareAndSetLeft(Node expect, Node update){
     return leftUpdater.compareAndSet(this, expect, update);
   }
   // ... and so on
 }

compareAndSetLeft 方法原子的将left指针从expect修改为update。我们知道,通过CAS实现的并发链表,其并发访问性能是最好的,因为无需给节点进行加锁,上面的代码是一个多线程访问的双向链表,拥有良好的读性能。

总结

通过上文的分析,总结 AtomicLongFieldUpdate 的使用场景主要如下(对另外两个也适用):

  • 多数情况下对属性操作为正常的读写,偶尔需要原子操作(CAS)
  • 该对象在系统中大量存在(如Record),需要节省AtomicLong的内置原子引用所带来的内存消耗.

据说ConcurrentHashMap, ConcurrentLinkedQueue和ConcurrentSkipListMap中有对AtomicXFieldUpdater工具类的使用,有机会可以通过源码加深一下理解,也可以读一下AtomicXFieldUpdater类的源码,对于AtomicLong和AtomicLongFieldUpdate的内存空间比较可以参考 引文6


以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持 码农网

查看所有标签

猜你喜欢:

本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们

编程风格

编程风格

[美] Cristina Videira Lopes / 顾中磊 / 人民邮电出版社 / 2017-8 / 55.00元

本书对一个常见的编程问题定义了不同的约束,分别使用33种方法实现了同一个词频统计任务,从而形成了风格迥异的编程风格。作者以惯用的计算机语言与简单的任务为画笔,描绘了一次生动难忘的编程之旅,帮助读者加深了对语言的理解,也提供了崭新的编程思路。一起来看看 《编程风格》 这本书的介绍吧!

HTML 压缩/解压工具
HTML 压缩/解压工具

在线压缩/解压 HTML 代码

RGB转16进制工具
RGB转16进制工具

RGB HEX 互转工具

随机密码生成器
随机密码生成器

多种字符组合密码