Java中12个原子(Atomic)操作类实现原理分析

Java中12个原子(Atomic)操作类实现原理分析前言我们知道i++操作实际上是线程不安全的,因为一个i++操作分为了三步:1、获取的i的值2、执行i+13、将i+1的结果赋值给i而这三步不是一

大家好,欢迎来到IT知识分享网。

前言

我们知道i++操作实际上是线程不安全的,因为一个i++操作分为了三步:

  • 1、获取的i的值
  • 2、执行i+1
  • 3、将i+1的结果赋值给i

而这三步不是一个原子操作,多线程环境下就会出现线程不安全性问题。

Java从JDK 1.5开始,在java.util.concurrent.atomic包下提供了12个对应的原子类操作,让我们可以直接使用原子操作类来实现一个原子的i++操作。

Java中一共提供了12个原子类操作,可以分为四种类型,分别是:

  • 原子更新基本类型
  • 原子更新数组
  • 原子更新引用
  • 原子更新属性

下面就让我们一起来看看这四大类中的12个原子操作类:

原子更新基本类型

原子更新基本数据类型有以下三个:

  • AtomicInteger:原子更新整型。
  • AtomicBoolean:原子更新布尔类型。
  • AtomicLong:原子更新长整型。

AtomicInteger

常用方法如下:

  • int addAndGet(int delta):
    以原子方式将传入的数值与实例中的值(AtomicInteger里的 value)相加,并返回结果。
  • boolean compareAndSet(int expect,int update):
    如果输入的数值等于预期值,则以原子方式将该值设置为输入的值,成功返回true,替换失败则返回false
  • int getAndIncrement():
    以原子方式将当前值自增1,并返回自增前的值
  • int getAndDecrement():
    以原子方式将当前值自减1,并返回自减前的值
  • void lazySet(int newValue):
    最终会设置成 newValue,注意这个方法是一个lazy方法,也就是说设置之后,并不会马上去将值更新到主内存,那么其他线程在一小段时间内可能看不到设置的值。
  • int getAndSet(int newValue):
    以原子方式将值设置为newValue,并返回旧值

代码示例

package com.zwx.concurrent.atomic; import java.util.concurrent.atomic.AtomicInteger; public class TestAtomicBasicData { public static void main(String[] args) throws InterruptedException { AtomicInteger atomicInteger = new AtomicInteger(8); System.out.println("初始化:" + atomicInteger);//8 atomicInteger.compareAndSet(8,10); System.out.println("CAS后:" + atomicInteger);//10 System.out.println(atomicInteger.getAndIncrement());//自增1,返回自增前的值10 System.out.println("自增后:" + atomicInteger);//11 System.out.println(atomicInteger.getAndDecrement());//11 System.out.println("自减后:" + atomicInteger);//10 } }

AtomicBoolean

更新boolean值,常用方法如下:

  • boolean compareAndSet(boolean expect,boolean update):
    如果输入的数值等于预期值,则以原子方式将该值设置为输入的值,成功返回true,替换失败则返回false。注意这里面实际上会先将boolean值转换为int类型,0-否 1-是
  • void lazySet(boolan newValue):
    最终会设置成 newValue,注意这个方法是一个lazy方法,也就是说设置之后,并不会马上去将值更新到主内存,那么其他线程在一小段时间内可能看不到设置的值。
  • boolean getAndSet(boolean newValue):
    以原子方式将值设置为newValue,并返回旧值

AtomicLong

这个和上面的AtomicInteger几乎是一样的,就不在举例了。
原子操作都是利用Unsafe类中的CAS操作实现的,但是Unsafe中只提供了三种类型的CAS操作:

Java中12个原子(Atomic)操作类实现原理分析

所以上面的boolean类型是转换为整型来CAS的,其他数据类型也可以先进行数据转换之后再通过CAS实现原子操作。

原子更新数组

原子操作更新数组也提供了三种类型:

  • AtomicIntegerArray:原子更Integer类型数组里的元素。
  • AtomicLongArray:原子更新Long类型数组里的元素。
  • AtomicReferenceArray:原子更新引用类型数组里的元素。

AtomicIntegerArray

 int[] arr = new int[]{1,2,3}; AtomicIntegerArray atomicIntegerArray = new AtomicIntegerArray(arr);
Java中12个原子(Atomic)操作类实现原理分析

可以看到初始化之后将数组复制了一份,所以不会如果把值改变了,不会影响原有数组的值。

boolean compareAndSet(int i, int expect, int update):可以看到这个方法对比基本类型多了一个i,也就是index下标,其他方法也是一样和基本类型数组相比,多了一个index参数。

代码示例

package com.zwx.concurrent.atomic; import java.util.concurrent.atomic.AtomicIntegerArray; public class TestAtomicArray { public static void main(String[] args) { int[] arr = new int[]{1,2,3}; AtomicIntegerArray atomicIntegerArray = new AtomicIntegerArray(arr); atomicIntegerArray.compareAndSet(1,2,8); System.out.println(arr[1]);//原数组的值没被改变,还是2 System.out.println(atomicIntegerArray.get(1));//atomicIntegerArray值被改变成8 } }

AtomicLongArray

和基本类型AtomicLong相比,方法都一样,也是多了一个index数组下标参数。

AtomicReferenceArray

这个方法也是一样,唯一的区别是可以传入一个泛型,也就是说数据中的元素时自定义的对象,而不是引用对象。

代码示例:

package com.zwx.concurrent.atomic; import com.alibaba.fastjson.JSONObject; import java.util.concurrent.atomic.AtomicReferenceArray; public class TestAtomicReferenceArray { public static void main(String[] args) { Man man = new Man(18,"张三"); Man[] arr = new Man[]{man}; AtomicReferenceArray<Man> atomicReferenceArray = new AtomicReferenceArray<>(arr); System.out.println("CAS前:" + JSONObject.toJSONString(atomicReferenceArray.get(0)));//{"age":18,"name":"张三"} Man updateMan = new Man(28,"李四"); atomicReferenceArray.compareAndSet(0,man,updateMan); System.out.println("CAS前:" + JSONObject.toJSONString(atomicReferenceArray.get(0)));//{"age":28,"name":"李四"} } } class Man{ protected volatile Integer age; private String name; public Man(Integer age, String name) { this.age = age; this.name = name; } public Integer getAge() { return age; } public void setAge(Integer age) { this.age = age; } public String getName() { return name; } public void setName(String name) { this.name = name; } }

原子更新引用类型

原子更新基本类型时每次只能更新一个变量,如果我们需要原子更新多个变量,怎么做呢?这时候我们可以把多个变量和合成一个,那么就需要使用这个原子更新引用类型提供的类来更新了。

原子更新引用类型也提供了3个类:

  • AtomicReference:原子更新引用类型。
  • AtomicMarkableReference:原子更新带有标记位的引用类型。可以原子更新一个布尔类 型的标记位和引用类型。
  • AtomicStampedReference:原子更新带有版本号的引用类型。该类将整数值与引用关联起来,可用于原子的更新数据和数据的版本号,可以解决使用CAS进行原子更新时可能出现的ABA问题。

AtomicReference

需要先构造一个引用对象然后调用AtomicReference中的相关原子方法,我们先来看一段代码示例:

代码示例

package com.zwx.concurrent.atomic; import com.alibaba.fastjson.JSONObject; import java.util.concurrent.atomic.AtomicReference; public class TestAtomicReference { public static void main(String[] args) { User oldUser = new User(18,"张三"); AtomicReference<User> atomicReference = new AtomicReference<>(oldUser); System.out.println("CAS前:" + JSONObject.toJSONString(atomicReference.get()));//{"age":18,"name":"张三"} User upateUser = new User(28,"李四"); boolean result =atomicReference.compareAndSet(oldUser,upateUser); System.out.println("CAS结果为:" + result);//true System.out.println("CAS后:" + JSONObject.toJSONString(atomicReference.get()));//{"age":28,"name":"李四"} } } class User{ volatile Integer age; private String name; public User(Integer age, String name) { this.age = age; this.name = name; } public Integer getAge() { return age; } public void setAge(Integer age) { this.age = age; } public String getName() { return name; } public void setName(String name) { this.name = name; } }

这里的原理也是一样,通过Uasafe的CAS操作Object对象实现原子操作:

Java中12个原子(Atomic)操作类实现原理分析

AtomicMarkableReference

这个和上面AtomicReference基本一致,唯一的区别是多了一个mark标记,boolean类型。

示例

package com.zwx.concurrent.atomic; import com.alibaba.fastjson.JSONObject; import java.util.concurrent.atomic.AtomicMarkableReference; public class TestAtomicReferenceMark { public static void main(String[] args) { Person person = new Person(18,"张三"); AtomicMarkableReference atomicMarkableReference = new AtomicMarkableReference(person,false); System.out.println("是否被标记过:" + atomicMarkableReference.isMarked()); System.out.println("CAS前:" + JSONObject.toJSONString(atomicMarkableReference.getReference()));//{"age":18,"name":"张三"} Person updatePerson = new Person(28,"李四"); /** * arg1:表示预期的引用对象 * arg2:表示即将更新的引用对象 * arg3:表示预期的标记 * arg4:表示更新的标记 * 需要参数1和参数3都是预期值才会CAS成功 */ atomicMarkableReference.compareAndSet(person,updatePerson,false,true); System.out.println("CAS后:" + JSONObject.toJSONString(atomicMarkableReference.getReference()));//{"age":28,"name":"李四"} } } class Person{ private Integer age; private String name; public Person(Integer age, String name) { this.age = age; this.name = name; } public Integer getAge() { return age; } public void setAge(Integer age) { this.age = age; } public String getName() { return name; } public void setName(String name) { this.name = name; } }

AtomicMarkableReference原理分析

Java中12个原子(Atomic)操作类实现原理分析

初始化对象的时候需要初始化一个引用对象和一个初始mark,而这两个属性又是通过其静态内部类Pair来管理的:

Java中12个原子(Atomic)操作类实现原理分析

AtomicStampedReference

这个和AtomicMarkableReference几乎一模一样,唯一的区别就是AtomicMarkableReference中的标记只有true和false,而AtomicStampedReference中的标记是一个int类型,可以视作版本号,可以解决CAS的ABA问题。

Java中12个原子(Atomic)操作类实现原理分析

原子更新属性

  • AtomicIntegerFieldUpdater:原子更新整型的字段的更新器。
  • AtomicLongFieldUpdater:原子更新长整型字段的更新器。
  • AtomicReferenceFieldUpdater:原子更新引用类型里的任意指定字段。

AtomicIntegerFieldUpdater

这个是用来更新引用对象中的int类型的属性,利用反射修改属性。有以下几点需要注意:

  • 引用类型中的属性必须是int,不能是包装类Integer
  • 引用类型中的属性必须被volatile修饰
  • 引用类型中的属性不能被private修饰

代码示例

package com.zwx.concurrent.atomic; import java.util.concurrent.atomic.AtomicIntegerFieldUpdater; public class TestAtomicReferenceField { public static void main(String[] args) { //AtomicIntegerFieldUpdater Women women = new Women(18,"张三"); //arg1:引用的对象类型 arg2:要修改的对象中的属性名 AtomicIntegerFieldUpdater atomicIntegerFieldUpdater = AtomicIntegerFieldUpdater.newUpdater(Women.class,"age"); atomicIntegerFieldUpdater.compareAndSet(women,18,28); System.out.println("CAS后的值:" + women.getAge());//28 } } class Women{ volatile int age; private String name; public Women(int age, String name) { this.age = age; this.name = name; } public Integer getAge() { return age; } public void setAge(Integer age) { this.age = age; } public String getName() { return name; } public void setName(String name) { this.name = name; } }

newUpdater方法:

Java中12个原子(Atomic)操作类实现原理分析

AtomicIntegerFieldUpdaterImpl中初始化中就是利用反射获取属性修改属性,并进行了一些验证:

Java中12个原子(Atomic)操作类实现原理分析

AtomicLongFieldUpdater

这个和上面AtomicLongFieldUpdater基本一样,用来更新long类型的属性,同样有以下几点需要注意:

  • 引用类型中的属性必须是long,不能是包装类Long
  • 引用类型中的属性必须被volatile修饰
  • 引用类型中的属性不能被private修饰

AtomicReferenceFieldUpdater

上面两个都是只能更新指定数据类型,而这个可以更新任意指定类型的属性。也有以下几个注意点:

  • 引用类型中的属性不能是原始数据类型,必须用对应包装类(这点和上面的两种相反)
  • 引用类型中的属性必须被volatile修饰
  • 引用类型中的属性不能被private修饰

示例

package com.zwx.concurrent.atomic; import com.alibaba.fastjson.JSONObject; import java.util.concurrent.atomic.AtomicReferenceFieldUpdater; public class TestAtomicReferenceField { public static void main(String[] args) { /** * arg1:传入引用对象类型 * arg2:传入引用对象的属性类型 * arg3:传入要修改的属性名 */ AtomicReferenceFieldUpdater atomicReferenceFieldUpdater1 = AtomicReferenceFieldUpdater.newUpdater(Women.class,Integer.class,"age"); AtomicReferenceFieldUpdater atomicReferenceFieldUpdater2 = AtomicReferenceFieldUpdater.newUpdater(Women.class,String.class,"name"); Women women = new Women(18,"张三"); atomicReferenceFieldUpdater1.compareAndSet(women,18,28); atomicReferenceFieldUpdater2.compareAndSet(women,"张三","李四"); System.out.println(JSONObject.toJSONString(women));//{"age":28,"name":"李四"} } } class Women{ volatile Integer age; volatile String name; public Women(int age, String name) { this.age = age; this.name = name; } public Integer getAge() { return age; } public void setAge(Integer age) { this.age = age; } public String getName() { return name; } public void setName(String name) { this.name = name; } }

CAS操作ABA问题

前面在讲述AQS同步队列的文章里提到了ABA问题可以通过引入一个版本号来和实际值拼在一起来避免ABA问题,那么实际上我们同样可以利用AtomicMarkableReference和AtomicStampedReference来实现,不过如果用AtomicMarkableReference只有true和false两种标记,而对于AtomicStampedReference就更自由,实际可以根据业务需求进行选择。

总结

本文介绍了Java中提供的12种原子操作类,原理均是通过Unsafe类中的CAS方法实现的,一般的CAS方法有可能会出现ABA问题,所以有一种带标记,一种带版本号的原子操作可以用于避免ABA问题的产生。

下一篇,将会介绍线程池的实现原理。

作者:刀哥谈Java
链接:https://juejin.im/post/

免责声明:本站所有文章内容,图片,视频等均是来源于用户投稿和互联网及文摘转载整编而成,不代表本站观点,不承担相关法律责任。其著作权各归其原作者或其出版社所有。如发现本站有涉嫌抄袭侵权/违法违规的内容,侵犯到您的权益,请在线联系站长,一经查实,本站将立刻删除。 本文来自网络,若有侵权,请联系删除,如若转载,请注明出处:https://yundeesoft.com/48059.html

(0)
上一篇 2024-09-12 11:00
下一篇 2024-09-12 18:15

相关推荐

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注

关注微信