“全栈2019”Java原子操作第十章:atomic包下字段原子更新器介绍

300 阅读25分钟

难度

初级

学习时间

30分钟

适合人群

零基础

开发语言

Java

开发环境

  • JDK v11
  • IntelliJIDEA v2018.3

友情提示

  • 本教学属于系列教学,内容具有连贯性,本章使用到的内容之前教学中都有详细讲解。
  • 本章内容针对零基础或基础较差的同学比较友好,可能对于有基础的同学来说很简单,希望大家可以根据自己的实际情况选择继续看完或等待看下一篇文章。谢谢大家的谅解!

1.温故知新

前面在《“全栈2019”Java原子操作第五章:AtomicInteger介绍与使用》一章中介绍了什么是原子操作类AtomicInteger

《“全栈2019”Java原子操作第六章:AtomicInteger灵活的运算方式》一章中介绍了使用原子操作类AtomicInteger的方法实现更灵活的运算方式

《“全栈2019”Java原子操作第七章:AtomicLong介绍与使用》一章中介绍了什么是原子操作类AtomicLong

《“全栈2019”Java原子操作第八章:AtomicReference介绍与使用》一章中介绍了什么是原子操作类AtomicReference<V>

《“全栈2019”Java原子操作第九章:atomic包下原子数组介绍与使用》一章中介绍了什么是原子数组AtomicIntegerArray、AtomicLongArray和AtomicReferenceArray<E>

现在介绍字段原子更新器AtomicIntegerFieldUpdater<T>、AtomicLongFieldUpdater<T>和AtomicReferenceFieldUpdater<T,​V>。

2.什么是字段原子更新器?

顾名思义,字段原子更新器就是以原子的方式去更新对象中的字段。

3.什么是字段?

字段就是类中的成员变量。

“全栈2019”Java原子操作第十章:atomic包下字段原子更新器介绍

如上图所示,Person类有一个成员变量age。

还不清楚字段的小伙伴请查阅《“全栈2019”Java第三十七章:类与字段》一文。

4.不包含类变量吗?

不包含类变量。字段更新器只能更新成员变量,不能更新类变量。

“全栈2019”Java原子操作第十章:atomic包下字段原子更新器介绍

还不清楚类变量的小伙伴请查阅《“全栈2019”Java第四十一章:static关键字》一文。

5.还有其他什么需求不?

成员变量要求被volatile关键字所修饰。

“全栈2019”Java原子操作第十章:atomic包下字段原子更新器介绍

如上图所示,成员变量被volatile关键字所修饰。

6.有访问权限限制不?

“全栈2019”Java原子操作第十章:atomic包下字段原子更新器介绍

有。首先private、protected、package、public这四个关键字都可以用来修饰成员变量,但是,我们字段原子更新器去访问它们所修饰的变量时,需要遵循它们的访问规则。

7.字段原子更新器以何种方式去更新字段的值?

字段原子更新器是以反射的方式去修改指定字段的值。

8.和getter、setter有没有关系?

和getter、setter没有任何关系,提不提供和getter、setter对字段的更新没有任何影响。

注:如果大家还有其他什么疑问请在评论区留言,谢谢!

接下来,我们就去看看atomic包下有哪些可供我们使用的类。

在此之前,原子操作的概念和必备知识在该系列的《“全栈2019”Java原子操作第一章:内存可见性volatile关键字解析》《“全栈2019”Java原子操作第二章:i++是原子操作吗?何为原子性》以及 《“全栈2019”Java原子操作第三章:比较并交换CAS技术详解》三章中已经详细介绍过了,这里就不在赘述。不清楚的小伙伴请前去查阅相关章节。

9.字段原子更新器有哪些?

java.util.concurrent.atomic包下一共有三个字段原子更新器:AtomicIntegerFieldUpdater<T>、AtomicLongFieldUpdater<T>和AtomicReferenceFieldUpdater<T,​V>。

其中:

AtomicIntegerFieldUpdater<T>是一个以原子方式更新对象中int类型字段的类。

AtomicLongFieldUpdater<T>是一个以原子方式更新对象中long类型字段的类。

AtomicReferenceFieldUpdater<T,​V>是一个以原子方式更新对象中对象类型字段的类。

千万注意!!!

AtomicIntegerFieldUpdater<T>只能更新int类型字段不能更新Integer类型字段。

AtomicLongFieldUpdater<T>只能更新long类型字段不能更新Long类型字段。

int与Integer和long与Long都是两种不同的类型。

如果想要更新Integer和Long类型请使用AtomicReferenceFieldUpdater<T,​V>。

10.字段原子更新器创建方式

这里需要注意的是字段原子更新器使用的是newUpdater()方法来创建对象的,而不是使用构造方法来创建对象的

这是因为我们字段原子更新器都是抽象类

“全栈2019”Java原子操作第十章:atomic包下字段原子更新器介绍

AtomicIntegerFieldUpdater<T>

“全栈2019”Java原子操作第十章:atomic包下字段原子更新器介绍

AtomicLongFieldUpdater<T>

“全栈2019”Java原子操作第十章:atomic包下字段原子更新器介绍

AtomicReferenceFieldUpdater<T,​V>

所以我们字段原子更新器提供了一个专门用来创建对象的方法:newUpdater()。

newUpdater()方法是静态的,可以直接通过类名调用。

下面分别来看看AtomicIntegerFieldUpdater<T>、AtomicLongFieldUpdater<T>和AtomicReferenceFieldUpdater<T,​V>它们的创建方式。

AtomicIntegerFieldUpdater<T>的创建方式:

  • AtomicIntegerFieldUpdater.newUpdater​(Class<U> tclass, String fieldName)

AtomicLongFieldUpdater<T>的创建方式:

  • AtomicLongFieldUpdater.newUpdater​(Class<U> tclass, String fieldName)

AtomicReferenceFieldUpdater<T,​V>的创建方式:

  • AtomicReferenceFieldUpdater.newUpdater​(Class<U> tclass, Class<W> vclass, String fieldName)

AtomicIntegerFieldUpdater<T>和AtomicLongFieldUpdater<T>中的newUpdater()方法参数解释

我们来一个示例,以AtomicIntegerFieldUpdater<T>为例:

“全栈2019”Java原子操作第十章:atomic包下字段原子更新器介绍

第一个参数Class<U> tclass的作用是指定更新对象的Class,例如你想更新Person对象中的某个字段,那么就传入Person.class:

“全栈2019”Java原子操作第十章:atomic包下字段原子更新器介绍

第二个参数String fieldName的作用是指定要更新的字段的名称,例如你想更新Person对象中的age字段,那么就传入字符串"age"即可:

“全栈2019”Java原子操作第十章:atomic包下字段原子更新器介绍

AtomicReferenceFieldUpdater<T,​V>中newUpdater()方法参数解释

我们来一个AtomicReferenceFieldUpdater<T,​V>示例:

“全栈2019”Java原子操作第十章:atomic包下字段原子更新器介绍

第一个参数Class<U> tclass的作用是指定更新对象的Class,例如你想更新Person对象中的某个字段,那么就传入Person.class:

“全栈2019”Java原子操作第十章:atomic包下字段原子更新器介绍

第二个参数Class<W> vclass的作用是指定要更新的字段的Class,例如你想更新Person对象中的String类型的name,那么就传入String.class:

“全栈2019”Java原子操作第十章:atomic包下字段原子更新器介绍

第三个参数String fieldName的作用是指定要更新的字段的名称,例如你想更新Person对象中的name字段,那么就传入字符串"name"即可:

“全栈2019”Java原子操作第十章:atomic包下字段原子更新器介绍

以上只是演示了创建示例,具体的应用在下面。

11.为什么需要字段原子更新器?

下面我们就来说说为什么需要字段原子更新器,并且就拿三个字段原子更新器中的之一:AtomicIntegerFieldUpdater<T>来说明问题。

注:AtomicIntegerFieldUpdater<T>、AtomicLongFieldUpdater<T>和AtomicReferenceFieldUpdater<T,​V>三个字段原子更新器只是更新字段的类型不同。

如果没有字段原子更新器的话,在多线程更新字段值的情况下,会出现并发问题。【注:同步除外】

先来看一个多线程更新Person类中int字段的场景。

首先,创建一个Person类并定义好公开的成员变量age:

“全栈2019”Java原子操作第十章:atomic包下字段原子更新器介绍

Person对象一旦被创建出来,那么age的初始值就为0。

Person类书写完毕。

接着,去Main类中创建一个Person对象:

“全栈2019”Java原子操作第十章:atomic包下字段原子更新器介绍

然后,创建出一个更新Person对象中成员变量age的任务:

“全栈2019”Java原子操作第十章:atomic包下字段原子更新器介绍

接着,在run()方法里面对age进行自增:

“全栈2019”Java原子操作第十章:atomic包下字段原子更新器介绍

run()方法书写完毕。

接下来,我们循环创建100个线程去执行更新字段的任务:

“全栈2019”Java原子操作第十章:atomic包下字段原子更新器介绍

最后,输出Person对象中的age字段:

“全栈2019”Java原子操作第十章:atomic包下字段原子更新器介绍

不过,延迟1秒钟再进行输出:

“全栈2019”Java原子操作第十章:atomic包下字段原子更新器介绍

例子书写完毕。

运行程序,执行结果:

“全栈2019”Java原子操作第十章:atomic包下字段原子更新器介绍

从运行结果来看,符合预期。

Person对象中的age字段被递增了100次,结果为100,正确。

但是我们只要对程序稍加改动,结果就有所不同。

下面,我们改写例子。

在更新字段的任务中,每次递增age之前使当前线程睡100毫秒:

“全栈2019”Java原子操作第十章:atomic包下字段原子更新器介绍

例子改写完毕。

运行程序,执行结果:

“全栈2019”Java原子操作第十章:atomic包下字段原子更新器介绍

从运行结果来看,符合预期。

程序稍加改动之后,我们更新字段的结果就和之前的不同,这就是多线程操作同一字段产生的问题。

可以使用volatile关键字解决吗?

我们来试试。

将Person类中的成员变量age用volatile关键字修饰一下:

“全栈2019”Java原子操作第十章:atomic包下字段原子更新器介绍

例子改写完毕。

运行程序,执行结果:

“全栈2019”Java原子操作第十章:atomic包下字段原子更新器介绍

从运行结果来看,不符合预期。

volatile关键字只能解决内存可见性问题,解决不了这种原子性问题。

看来单单只是使用volatile关键字是不行的,那怎么办?

我们只要对Person类中的字段以原子方式更新即可。

有什么方法可以实现对Person类中的字段以原子方式更新呢?

对于int类型字段而言,可以使用AtomicIntegerFieldUpdater<T>字段原子更新器。

下面,我们就使用AtomicIntegerFieldUpdater<T>来更新Person对象中的字段。

改写例子,先创建出AtomicIntegerFieldUpdater<T>对象:

“全栈2019”Java原子操作第十章:atomic包下字段原子更新器介绍

然后,我们在run()方法中使用“fieldUpdater.getAndIncrement(person);”替换“person.age++”

“全栈2019”Java原子操作第十章:atomic包下字段原子更新器介绍

getAndIncrement(T obj)方法的作用是递增指定字段。

例子改写完毕。

运行程序,执行结果:

“全栈2019”Java原子操作第十章:atomic包下字段原子更新器介绍

从运行结果来看,符合预期。

因为AtomicIntegerFieldUpdater<T>是以原子方式更新对象中的字段,所以即使在多线程的情况下也不会产生数据不一致问题。

以上就是使用普通方式更新对象中的字段和使用AtomicIntegerFieldUpdater<T>更新对象中的字段的区别。

接下来,看看三个字段原子更新器中的常用方法,它们大多数是一样的,只是参数类型不同而已。

12.AtomicIntegerFieldUpdater<T>常用方法

  • get(T obj)方法的作用是获取指定对象的字段。
  • set(T obj, int newValue)方法的作用是设置指定对象的字段的值。参数obj是对象,newValue是我们可以指定的新值。
  • getAndIncrement(T obj)方法的作用是递增指定对象的字段的值,返回递增前的值。
  • incrementAndGet(T obj)方法的作用是递增指定对象的字段的值,返回递增后的值。
  • getAndDecrement(T obj)方法的作用是递减指定对象的字段的值,返回递减前的值。
  • decrementAndGet(T obj)方法的作用是递减指定对象的字段的值,返回递减后的值。
  • getAndAdd(T obj, int delta)方法的作用是指定对象的字段与delta相加,返回相加前的值。
  • addAndGet(T obj, int delta)方法的作用是指定对象的字段与delta相加,返回相加后的值。
  • getAndUpdate(T obj, IntUnaryOperator updateFunction)方法的作用是更新指定对象的字段,更新方式由参数updateFunction决定,方法返回更新前的值。
  • updateAndGet(T obj, IntUnaryOperator updateFunction)方法的作用是更新指定对象的字段,更新方式由参数updateFunction决定,方法返回更新后的值。
  • getAndAccumulate(T obj, int x, IntBinaryOperator accumulatorFunction)方法的作用是指定对象的字段与参数x进行二元运算,运算方式由参数accumulatorFunction决定,方法返回运算前的值。
  • getAndAccumulate(T obj, int x, IntBinaryOperator accumulatorFunction)方法的作用是指定对象的字段与参数x进行二元运算,运算方式由参数accumulatorFunction决定,方法返回运算后的值。
  • compareAndSet(T obj, int expect, int update)方法的作用是如果当value==expect,那么就将update赋给value,否则什么也不做。

13.AtomicLongFieldUpdater<T>常用方法

  • get(T obj)方法的作用是获取指定对象的字段。
  • set(T obj, long newValue)方法的作用是设置指定对象的字段的值。参数obj是对象,newValue是我们可以指定的新值。
  • getAndIncrement(T obj)方法的作用是递增指定对象的字段的值,返回递增前的值。
  • incrementAndGet(T obj)方法的作用是递增指定对象的字段的值,返回递增后的值。
  • getAndDecrement(T obj)方法的作用是递减指定对象的字段的值,返回递减前的值。
  • decrementAndGet(T obj)方法的作用是递减指定对象的字段的值,返回递减后的值。
  • getAndAdd(T obj, long delta)方法的作用是指定对象的字段与delta相加,返回相加前的值。
  • addAndGet(T obj, long delta)方法的作用是指定对象的字段与delta相加,返回相加后的值。
  • getAndUpdate(T obj, IntUnaryOperator updateFunction)方法的作用是更新指定对象的字段,更新方式由参数updateFunction决定,方法返回更新前的值。
  • updateAndGet(T obj, IntUnaryOperator updateFunction)方法的作用是更新指定对象的字段,更新方式由参数updateFunction决定,方法返回更新后的值。
  • getAndAccumulate(T obj, long x, IntBinaryOperator accumulatorFunction)方法的作用是指定对象的字段与参数x进行二元运算,运算方式由参数accumulatorFunction决定,方法返回运算前的值。
  • getAndAccumulate(T obj, long x, IntBinaryOperator accumulatorFunction)方法的作用是指定对象的字段与参数x进行二元运算,运算方式由参数accumulatorFunction决定,方法返回运算后的值。
  • compareAndSet(T obj, long expect, long update)方法的作用是如果当value==expect,那么就将update赋给value,否则什么也不做。

14.AtomicReferenceFieldUpdater<T,​V>常用方法

  • get(T obj)方法的作用是获取指定对象的字段。
  • set(T obj, V newValue)方法的作用是设置指定对象的字段的值。参数obj是对象,newValue是我们可以指定的新值。
  • getAndUpdate(T obj, UnaryOperator<V> updateFunction)方法的作用是更新指定对象的字段,更新方式由参数updateFunction决定,方法返回更新前的值。
  • updateAndGet(T obj, UnaryOperator<V> updateFunction)方法的作用是更新指定对象的字段,更新方式由参数updateFunction决定,方法返回更新后的值。
  • getAndAccumulate(T obj, V x, BinaryOperator<V> accumulatorFunction)方法的作用是指定对象的字段与参数x进行二元运算,运算方式由参数accumulatorFunction决定,方法返回运算前的值。
  • getAndAccumulate(T obj, V x, BinaryOperator<V> accumulatorFunction)方法的作用是指定对象的字段与参数x进行二元运算,运算方式由参数accumulatorFunction决定,方法返回运算后的值。
  • compareAndSet(T obj, V expect, V update)方法的作用是如果当value==expect,那么就将update赋给value,否则什么也不做。

接下来是方法示例。

注意:三个字段原子更新器的方法用法一样,只是参数类型不同,所以这里选取AtomicIntegerFieldUpdater<T>对象进行方法演示。

15.获取指定对象的字段get(T obj)方法

get(T obj)方法的作用是获取指定对象的字段。

例如,我们创建AtomicIntegerFieldUpdater<T>对象和Person对象:

“全栈2019”Java原子操作第十章:atomic包下字段原子更新器介绍

然后,调用其get(T obj)方法获取对象中的字段并输出:

“全栈2019”Java原子操作第十章:atomic包下字段原子更新器介绍

例子书写完毕。

运行程序,执行结果:

“全栈2019”Java原子操作第十章:atomic包下字段原子更新器介绍

从运行结果来看,符合预期。

获取完对象中的字段之后,怎么设置指定对象中的字段的值呢?

16.设置指定对象的字段的值set(T obj, int newValue)方法

set(T obj, int newValue)方法的作用是设置指定对象的字段的值。参数obj是对象,newValue是我们可以指定的新值。

例如,我们创建一个AtomicIntegerFieldUpdater<T>对象和Person对象:

“全栈2019”Java原子操作第十章:atomic包下字段原子更新器介绍

然后,调用其set(T obj, int newValue)方法将person对象的age字段的值设置为18:

“全栈2019”Java原子操作第十章:atomic包下字段原子更新器介绍

接着,调用其get(T obj)方法获取person对象的age字段并输出:

“全栈2019”Java原子操作第十章:atomic包下字段原子更新器介绍

例子书写完毕。

运行程序,执行结果:

“全栈2019”Java原子操作第十章:atomic包下字段原子更新器介绍

从运行结果来看,符合预期。

设置完对象中的字段之后,怎么指定对象中的字段自增呢?

17.指定对象中的字段递增方法

AtomicIntegerFieldUpdater<T>类中有两个递增方法:

  • getAndIncrement(T obj)
  • incrementAndGet(T obj)

其中,getAndIncrement(T obj)方法相当于i++,incrementAndGet(T obj)方法相当于++i。

getAndIncrement(T obj)方法的作用是递增指定对象的字段的值,返回递增前的值。

incrementAndGet(T obj)方法的作用是递增指定对象的字段的值,返回递增后的值。

例如,我们创建AtomicIntegerFieldUpdater<T>对象和Person对象:

“全栈2019”Java原子操作第十章:atomic包下字段原子更新器介绍

先调用getAndIncrement(T obj)方法使person对象中的age字段递增一次并输出结果:

“全栈2019”Java原子操作第十章:atomic包下字段原子更新器介绍

接着,调用incrementAndGet(T obj)方法再使person对象中的age字段递增一次并输出结果:

“全栈2019”Java原子操作第十章:atomic包下字段原子更新器介绍

例子书写完毕。

运行程序,执行结果:

“全栈2019”Java原子操作第十章:atomic包下字段原子更新器介绍

从运行结果来看,符合预期。

person对象中的age字段初始值为0,getAndIncrement(T obj)方法返回递增前的值,也就是0,然后递增之后person对象中的age字段的值变为1,接着调用incrementAndGet(T obj)方法使其在1的基础上再递增一次变为2,结果为正确。

指定对象中的字段递增之后,怎么指定对象中的字段递减呢?

18.指定对象中的字段递减方法

AtomicIntegerFieldUpdater<T>类中有两个递减方法:

  • getAndDecrement(T obj)
  • decrementAndGet(T obj)

其中,getAndDecrement(T obj)方法相当于i--,decrementAndGet(T obj)方法相当于--i。

getAndDecrement(T obj)方法的作用是递减指定对象的字段的值,返回递减前的值。

decrementAndGet(T obj)方法的作用是递减指定对象的字段的值,返回递减后的值。

例如,我们创建AtomicIntegerFieldUpdater<T>对象和Person对象:

“全栈2019”Java原子操作第十章:atomic包下字段原子更新器介绍

先调用getAndDecrement(T obj)方法使person对象中的age字段递减一次并输出结果:

“全栈2019”Java原子操作第十章:atomic包下字段原子更新器介绍

接着,调用decrementAndGet(T obj)方法再使person对象中的age字段递减一次并输出结果:

“全栈2019”Java原子操作第十章:atomic包下字段原子更新器介绍

例子书写完毕。

运行程序,执行结果:

“全栈2019”Java原子操作第十章:atomic包下字段原子更新器介绍

从运行结果来看,符合预期。

person对象中的age字段初始值为0,getAndDecrement(T obj)方法返回递减前的值,也就是0,然后递减之后person对象中的age字段的值变为-1,接着调用decrementAndGet(T obj)方法使其在-1的基础上再递减一次变为-2,结果为正确。

指定对象中的字段递减之后,怎么指定对象中的字段与任意值相加呢?

19.指定对象中的字段与任意值相加方法

前面两小节演示的是递增和递减情况,如果想要指定对象中的字段加上任意数,比如+5,或者你想要减去任意数,比如-5,使用前面那几个方法是做不到的,所以AtomicIntegerFieldUpdater<T>类也为我们提供相应方法。

AtomicIntegerFieldUpdater<T>类中有两个指定对象中的字段与任意值相加方法:

  • getAndAdd(T obj, int delta)
  • addAndGet(T obj, int delta)

其中:

getAndAdd(T obj, int delta)方法的作用是指定对象的字段与delta相加,返回相加前的值。

addAndGet(T obj, int delta)方法的作用是指定对象的字段与delta相加,返回相加后的值。

例如,我们创建AtomicIntegerFieldUpdater<T>对象和Person对象:

“全栈2019”Java原子操作第十章:atomic包下字段原子更新器介绍

先调用getAndAdd(T obj, int delta)方法使person对象中的age字段与5相加并输出结果:

“全栈2019”Java原子操作第十章:atomic包下字段原子更新器介绍

接着,调用addAndGet(T obj, int delta)方法再使person对象中的age字段与-2相加并输出结果:

“全栈2019”Java原子操作第十章:atomic包下字段原子更新器介绍

例子书写完毕。

运行程序,执行结果:

“全栈2019”Java原子操作第十章:atomic包下字段原子更新器介绍

从运行结果来看,符合预期。

person对象中的age字段初始值为0,getAndAdd(T obj, int delta)方法返回相加前的值,也就是0,然后相加之后person对象中的age字段的值变为5,接着调用addAndGet(T obj, int delta)方法使其在5的基础上再相加一次-2变为3,结果为正确。

在了解指定对象中的字段与任意值相加之后,我想使用自定义方式更新指定对象中的字段该怎么做呢?

20.使用自定义方式更新指定对象中的字段

前面几个小节演示的都是和指定数的加法或减法,如果我想自定义更新指定对象中的字段的值怎么做呢?

为此,AtomicIntegerFieldUpdater<T>类提供了两个使用自定义方式更新指定对象中的字段的值的方法:

  • getAndUpdate(T obj, IntUnaryOperator updateFunction)
  • updateAndGet(T obj, IntUnaryOperator updateFunction)

其中:

getAndUpdate(T obj, IntUnaryOperator updateFunction)方法的作用是更新指定对象的字段,更新方式由参数updateFunction决定,方法返回更新前的值。

updateAndGet(T obj, IntUnaryOperator updateFunction)方法的作用是更新指定对象的字段,更新方式由参数updateFunction决定,方法返回更新后的值。

参数解释

T obj:指定的对象。

IntUnaryOperator updateFunction:更新方法实现。

其中,IntUnaryOperator是一个接口:

“全栈2019”Java原子操作第十章:atomic包下字段原子更新器介绍

我们只需实现IntUnaryOperator接口中的applyAsInt(int operand)方法即可:

“全栈2019”Java原子操作第十章:atomic包下字段原子更新器介绍

applyAsInt(int operand)方法是写如何更新字段的地方。

示例

首先,我们创建AtomicIntegerFieldUpdater<T>对象和Person对象:

“全栈2019”Java原子操作第十章:atomic包下字段原子更新器介绍

先调用getAndUpdate(T obj, IntUnaryOperator updateFunction)方法使person对象中的age字段与5相加并输出结果:

“全栈2019”Java原子操作第十章:atomic包下字段原子更新器介绍

接着,调用updateAndGet(T obj, IntUnaryOperator updateFunction)方法再使person对象中的age字段与2相乘并输出结果:

“全栈2019”Java原子操作第十章:atomic包下字段原子更新器介绍

例子书写完毕。

运行程序,执行结果:

“全栈2019”Java原子操作第十章:atomic包下字段原子更新器介绍

从运行结果来看,符合预期。

person对象中的age字段初始值为0,getAndUpdate(T obj, IntUnaryOperator updateFunction)方法返回递减前的值,也就是0,然后person对象中的age字段与5相加变为5,接着调用updateAndGet(T obj, IntUnaryOperator updateFunction)方法使其在5的基础上再乘以2变为10,结果为正确。

在了解使用自定义方式更新指定对象中的字段之后,我想指定对象中的字段与指定值进行二元运算该怎么做呢?

21.指定对象中的字段与指定值进行二元运算

如果我们想指定对象中的字段与指定值进行二元运算该怎么做呢?

为此,AtomicIntegerFieldUpdater<T>类提供了两个指定对象中的字段与指定值进行二元运算的方法:

  • getAndAccumulate(T obj, int x, IntBinaryOperator accumulatorFunction)
  • getAndAccumulate(T obj, int x, IntBinaryOperator accumulatorFunction)

其中:

getAndAccumulate(T obj, int x, IntBinaryOperator accumulatorFunction)方法的作用是指定对象的字段与参数x进行二元运算,运算方式由参数accumulatorFunction决定,方法返回运算前的值。

getAndAccumulate(T obj, int x, IntBinaryOperator accumulatorFunction)方法的作用是指定对象的字段与参数x进行二元运算,运算方式由参数accumulatorFunction决定,方法返回运算后的值。

参数解释

T obj:指定参与运算的指定对象的字段,即left值(结合IntBinaryOperator即可来看)。

int x:指定参与运算的值,即right值(结合IntBinaryOperator接口来看)。

IntBinaryOperator accumulatorFunction二元运算的方式。

其中,IntBinaryOperator是一个接口:

“全栈2019”Java原子操作第十章:atomic包下字段原子更新器介绍

我们只需实现IntBinaryOperator接口中的applyAsInt(int left, int right)方法即可:

“全栈2019”Java原子操作第十章:atomic包下字段原子更新器介绍

applyAsInt(int left, int right)方法是写如何将两个字段的值进行二元运算的地方。

示例

首先,我们创建AtomicIntegerFieldUpdater<T>对象和Person对象:

“全栈2019”Java原子操作第十章:atomic包下字段原子更新器介绍

先调用getAndAccumulate(T obj, int x, IntBinaryOperator accumulatorFunction)方法使person对象中的age字段与4相加并输出结果:

“全栈2019”Java原子操作第十章:atomic包下字段原子更新器介绍

接着,调用getAndAccumulate(T obj, int x, IntBinaryOperator accumulatorFunction)方法再使person对象中的age字段与5相乘并输出结果:

“全栈2019”Java原子操作第十章:atomic包下字段原子更新器介绍

例子书写完毕。

运行程序,执行结果:

“全栈2019”Java原子操作第十章:atomic包下字段原子更新器介绍

从运行结果来看,符合预期。

person对象中的age字段初始值为0,先调用getAndAccumulate(T obj, int x, IntBinaryOperator accumulatorFunction)方法返回递减前的值,也就是0,然后person对象中的age字段与4相加变为4,接着调用getAndAccumulate(T obj, int x, IntBinaryOperator accumulatorFunction)方法使其在4的基础上再乘以5变为20,结果为正确。

在了解指定对象中的字段与指定值进行二元运算之后,我想知道AtomicIntegerFieldUpdater<T>有体现CAS算法吗?

22.CAS算法体现

我们之前在《“全栈2019”Java原子操作第三章:比较并交换CAS技术详解》一章中学习过什么是CAS算法。

在AtomicIntegerFieldUpdater<T>类中也有体现:

“全栈2019”Java原子操作第十章:atomic包下字段原子更新器介绍

compareAndSet(T obj, int expect, int update)方法的作用是如果当value==expect,那么就将update赋给value,否则什么也不做。

参数解释

T obj:操作指定的对象中的字段。

int expect:预期值。

int update:新值。

示例

首先,我们创建AtomicIntegerFieldUpdater<T>对象和Person对象:

“全栈2019”Java原子操作第十章:atomic包下字段原子更新器介绍

然后,在调用compareAndSet(T obj, int expect, int update)方法之前获取一次person对象中的age字段:

“全栈2019”Java原子操作第十章:atomic包下字段原子更新器介绍

接着,调用compareAndSet(T obj, int expect, int update)方法并输出方法返回结果:

“全栈2019”Java原子操作第十章:atomic包下字段原子更新器介绍

最后,我们再获取一次person对象中的age字段:

“全栈2019”Java原子操作第十章:atomic包下字段原子更新器介绍

例子改写完毕。

运行程序,执行结果:

“全栈2019”Java原子操作第十章:atomic包下字段原子更新器介绍

从运行结果来看,符合预期。

当然了,你也可以将预期值改为一个非原值的数,这样赋值就不成功。

最后,希望大家可以把这个例子照着写一遍,然后再自己默写一遍,方便以后碰到类似的面试题可以轻松应对。

祝大家编码愉快!

GitHub

本章程序GitHub地址:https://github.com/gorhaf/Java2019/tree/master/Thread/atomic/字段原子更新器

总结

  • AtomicIntegerFieldUpdater<T>是一个以原子方式更新对象中int类型字段的类。
  • AtomicLongFieldUpdater<T>是一个以原子方式更新对象中long类型字段的类。
  • AtomicReferenceFieldUpdater<T,​V>是一个以原子方式更新对象中对象类型字段的类。
  • AtomicIntegerFieldUpdater<T>只能更新int类型字段不能更新Integer类型字段。
  • AtomicLongFieldUpdater<T>只能更新long类型字段不能更新Long类型字段。
  • 如果想要更新Integer和Long类型请使用AtomicReferenceFieldUpdater<T,​V>。
  • 字段原子更新器使用的是newUpdater()方法来创建对象的,而不是使用构造方法来创建对象的。
  • get(T obj)方法的作用是获取指定对象的字段。
  • set(T obj, int newValue)/set(T obj, long newValue)/set(T obj, V newValue)方法的作用是设置指定对象的字段的值。参数obj是对象,newValue是我们可以指定的新值。
  • getAndIncrement(T obj)方法的作用是递增指定对象的字段的值,返回递增前的值。【注:AtomicReferenceFieldUpdater<T,​V>没有此方法】
  • incrementAndGet(T obj)方法的作用是递增指定对象的字段的值,返回递增后的值。【注:AtomicReferenceFieldUpdater<T,​V>没有此方法】
  • getAndDecrement(T obj)方法的作用是递减指定对象的字段的值,返回递减前的值。【注:AtomicReferenceFieldUpdater<T,​V>没有此方法】
  • decrementAndGet(T obj)方法的作用是递减指定对象的字段的值,返回递减后的值。【注:AtomicReferenceFieldUpdater<T,​V>没有此方法】
  • getAndAdd(T obj, int delta)/getAndAdd(T obj, long delta)方法的作用是指定对象的字段与delta相加,返回相加前的值。【注:AtomicReferenceFieldUpdater<T,​V>没有此方法】
  • addAndGet(T obj, int delta)/addAndGet(T obj, long delta)方法的作用是指定对象的字段与delta相加,返回相加后的值。【注:AtomicReferenceFieldUpdater<T,​V>没有此方法】
  • getAndUpdate(T obj, IntUnaryOperator updateFunction)/getAndUpdate(T obj, IntUnaryOperator updateFunction)/getAndUpdate(T obj, UnaryOperator<V> updateFunction)方法的作用是更新指定对象的字段,更新方式由参数updateFunction决定,方法返回更新前的值。
  • updateAndGet(T obj, IntUnaryOperator updateFunction)/updateAndGet(T obj, IntUnaryOperator updateFunction)/updateAndGet(T obj, UnaryOperator<V> updateFunction)方法的作用是更新指定对象的字段,更新方式由参数updateFunction决定,方法返回更新后的值。
  • getAndAccumulate(T obj, int x, IntBinaryOperator accumulatorFunction)/getAndAccumulate(T obj, long x, IntBinaryOperator accumulatorFunction)/getAndAccumulate(T obj, V x, BinaryOperator<V> accumulatorFunction)方法的作用是指定对象的字段与参数x进行二元运算,运算方式由参数accumulatorFunction决定,方法返回运算前的值。
  • getAndAccumulate(T obj, int x, IntBinaryOperator accumulatorFunction)/getAndAccumulate(T obj, long x, IntBinaryOperator accumulatorFunction)/getAndAccumulate(T obj, V x, BinaryOperator<V> accumulatorFunction)方法的作用是指定对象的字段与参数x进行二元运算,运算方式由参数accumulatorFunction决定,方法返回运算后的值。
  • compareAndSet(T obj, int expect, int update)/compareAndSet(T obj, long expect, long update)/compareAndSet(T obj, V expect, V update)方法的作用是如果当value==expect,那么就将update赋给value,否则什么也不做。

至此,Java中字段原子更新器AtomicIntegerFieldUpdater<T>、AtomicLongFieldUpdater<T>和AtomicReferenceFieldUpdater<T,​V>相关内容讲解先告一段落,更多内容请持续关注。

答疑

如果大家有问题或想了解更多前沿技术,请在下方留言或评论,我会为大家解答。

上一章

“全栈2019”Java原子操作第九章:atomic包下原子数组介绍与使用

下一章

“全栈2019”Java原子操作第十一章:CAS与ABA问题介绍与探讨

学习小组

加入同步学习小组,共同交流与进步。

  • 方式一:关注头条号Gorhaf,私信“Java学习小组”。
  • 方式二:关注公众号Gorhaf,回复“Java学习小组”。

全栈工程师学习计划

关注我们,加入“全栈工程师学习计划”。

“全栈2019”Java原子操作第十章:atomic包下字段原子更新器介绍

版权声明

原创不易,未经允许不得转载!