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

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

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

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

AtomicXFieldUpdater

根据名字,我们可以知道 AtomicLongFieldUpdater 是对long型field进行原子update的,它是一个工具类,那么有long型的话是不是就有其他类型?通过查看相应的包,发现还有 AtomicIntegerFieldUpdater 和 AtomicReferenceFieldUpdater 两个“同胞兄弟”,它们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的时候,使用 AtomicLongFieldUpdater 的 incrementAndGet 方法来进行,这样内存空间节省了下来(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


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

查看所有标签

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

大话设计模式

大话设计模式

程杰 / 清华大学出版社 / 2007-12-1 / 45.00元

本书通篇都是以情景对话的形式,用多个小故事或编程示例来组织讲解GOF(设计模式的经典名著——Design Patterns:Elements of Reusable Object-Oriented Software,中译本名为《设计模式——可复用面向对象软件的基础》的四位作者EIich Gamma、Richard Helm、Ralph Johnson,以及John Vlissides,这四人常被称......一起来看看 《大话设计模式》 这本书的介绍吧!

UNIX 时间戳转换
UNIX 时间戳转换

UNIX 时间戳转换

RGB HSV 转换
RGB HSV 转换

RGB HSV 互转工具

RGB CMYK 转换工具
RGB CMYK 转换工具

RGB CMYK 互转工具