6.juc包下的原子类AtomicInteger,AtomicL
在介绍juc中的原子类之前,先看看官方文档对java.util.concurrent.atomic
包的介绍官方文档地址这里截取翻译之后的部分描述
1. 支持对单个变量进行无锁线程安全编程
2. 类的实例`AtomicBoolean`,`AtomicInteger`,`AtomicLong`和`AtomicReference` 每个提供访问和更新相应的类型的单个变量
3. 这些类不是 java.lang.Integer和相关类的通用替代品。他们没有 定义方法,如equals,hashCode和 compareTo。由于预期原子变量会发生突变,因此对于哈希表键,它们是较差的选择。
4. `AtomicIntegerArray`,`AtomicLongArray`和`AtomicReferenceArray`类进一步扩展到这些类型的数组原子操作的支持
5. `AtomicIntegerFieldUpdater`,`AtomicLongFieldUpdater`和`AtomicReferenceFieldUpdater`是基于反射的实用程序,它们提供对关联的字段类型的访问。这些主要用于原子数据结构,其中几个volatile同一节点的字段(例如,树节点的链接)独立地接受原子更新。这些类在如何以及何时使用原子更新方面提供了更大的灵活性,但代价是基于反射的设置更加笨拙,使用不方便且保证较弱。
6. `AtomicMarkableReference`类与引用关联的单个布尔值。例如,此位可能在数据结构内使用,表示所引用的对象在逻辑上已被删除。`AtomicStampedReference`类与引用关联的整数值。例如,这可以用于表示与一系列更新相对应的版本号。
在整个包中可以分为5种,这5中是我个人按照作用进行分类的
种类 | 包含的类 |
---|---|
普通原子操作类 |
AtomicBoolean ,AtomicInteger ,AtomicLong 和AtomicReference
|
数组操作类 |
AtomicIntegerArray ,AtomicLongArray 和AtomicReferenceArray
|
对象中字段操作类 |
AtomicIntegerFieldUpdater ,AtomicLongFieldUpdater 和AtomicReferenceFieldUpdater
|
标记操作类 |
AtomicMarkableReference 跟AtomicStampedReference
|
并发辅助计算类 |
DoubleAccumulator ,DoubleAdder ,LongAccumulator ,LongAdder 以及这些类的抽象父类Striped64
|
1.作用
从上面介绍中可以看出这个类的作用是用来对单个变量进行操作的,在并发环境下可以保证线程的安全性,其中保证安全的原因是使用CAS操作对变量进行操作的,对于这种CAS操作的实现可以查看前面的一篇文章CAS以及相关的底层实现。
2.普通原子操作类
个人划分的普通原子操作类是AtomicBoolean
,AtomicInteger
,AtomicLong
和AtomicReference
各个类的作用分别是
类名 | 作用 |
---|---|
AtomicBoolean |
一个可以原子更新值boolean值的类 |
AtomicInteger |
一个可以原子更新int类型变量值的类 |
AtomicLong |
一个可以原子更新long类型变量值的类 |
AtomicReference |
一个可以原子更新对象引用的类 |
2.1 AtomicInteger
,AtomicLong
类
AtomicLong
中的方法跟 AtomicInteger
中提供的方法是一模一样的,这里用代码简单演示一下AtomicLong
中的方法
public class AtomicTest {
public static void main(String[] args) {
AtomicInteger integer = new AtomicInteger();
//设置值
integer.set(1);
//跟set一样也是设置值,只不过set方法能保证可见性,而lazySet不行
integer.lazySet(2);
//CAS操作,比较交换
integer.compareAndSet(2,3);
//自增后获取值
integer.incrementAndGet();
//与指定的值相加后返回
integer.addAndGet(2);
//获取之后进行自增
integer.getAndIncrement();
System.out.println(integer.get());
}
}
这里只列举了部分的方法,大部分方法的作用看方法名就能明白。关于set
方法跟lazySet
的区别,这里可以看前面写的一篇文章java的JUC包下AtomicXXX中的set跟lazySet区别以及lazySet的原理。
2.2 AtomicBoolean
类
AtomicBoolean
其实是用一个int
类型的值来表示true
跟false
的,如果是true
则用1表示,如果是false
则用0表示。其初始化方法就可以看出来
public AtomicBoolean(boolean initialValue) {
value = initialValue ? 1 : 0;
}
因此其底层的实现的方式跟AtomicBoolean
是一样的,还是简单的列举一下部分方法
public class AtomicBooleanTest {
public static void main(String[] args) {
AtomicBoolean atomicBoolean = new AtomicBoolean(true);
System.out.println(atomicBoolean.get());
boolean setResult1 = atomicBoolean.compareAndSet(false, true);
System.out.println(setResult1);
boolean setResult2 = atomicBoolean.compareAndSet(true, false);
System.out.println(setResult2);
System.out.println(atomicBoolean.get());
}
}
运行结果
true
false
true
false
2.3 AtomicReference
类
AtomicReference
作用是更新一个对象。维护的是这个变量的地址值。在更新的时候校验这个的对象是不是初始化时候的对象的引用地址,不是则不给予更新,是的就更新。同时还可以定义更新的操作函数。这里列举部分测试代码
public class AtomicReferenceTest {
@Test
public void test() {
Object referenceOne = new Object();
Object referenceTwo = new Object();
AtomicReference<Object> atomicReferenceOne = new AtomicReference<>(referenceOne);
System.out.println(atomicReferenceOne.get());
//compareAndSet时候原始的对象必须是创建AtomicReference的时候设置的对象
boolean resultOne = atomicReferenceOne.compareAndSet(referenceTwo, referenceOne);
System.out.println(resultOne);
//compareAndSet时候原始的对象必须是创建AtomicReference的时候设置的对象
boolean resultTwo = atomicReferenceOne.compareAndSet(referenceOne, referenceTwo);
System.out.println(resultTwo);
//获取然后更新函数执行结果返回的值
atomicReferenceOne.getAndUpdate((one -> referenceTwo));
System.out.println(atomicReferenceOne.get());
}
}
3.数组操作类
用来操作数组的原子类AtomicIntegerArray
,AtomicLongArray
和AtomicReferenceArray
。分别对应操作int数组,long数组以及object
类型数组。
3.1AtomicIntegerArray
,AtomicLongArray
,AtomicReferenceArray
类
这里用AtomicIntegerArray
作为示例,因为这两个类实现是一样的,只不过接收的数据类型不一有。先看看对应的重要部分,偏移量的计算方式
static {
//获取当前数组的规模
int scale = unsafe.arrayIndexScale(long[].class);
//数组的大小需要是2的倍数
if ((scale & (scale - 1)) != 0)
throw new Error("data type scale not a power of two");
//计算当前数组的长度是2的多少倍
shift = 31 - Integer.numberOfLeadingZeros(scale);
}
private static long byteOffset(int i) {
//计算当前元素的实际偏移量=数组的起始偏移量+当前元素在数组的位置
return ((long) i << shift) + base;
}
知道了计算偏移量的方式,就好操作数组中的对应的元素了。所有的对数组中数据的操作,都是通过偏移量来进行的。
public class AtomicIntegerArrayTest {
@Test
public void test() {
AtomicIntegerArray array = new AtomicIntegerArray(new int[]{1, 2, 3, 4,5});
//获取指定index=2的数据
System.out.println(array.get(2));
//在指定index=2的数据上加上1后获取结果
System.out.println(array.addAndGet(2,1));
//获取指定index=2的数据
System.out.println(array.get(2));
//比较交换指定index=2的值为4,因为值是4而期望的3所以失败
System.out.println(array.compareAndSet(2,3,4));;
//比较交换指定index=2的值为3,因为值是4期望的4所以成功
System.out.println(array.compareAndSet(2,4,3));;
}
}
4.对象字段操作类
4.1AtomicIntegerFieldUpdater
,AtomicLongFieldUpdater
和AtomicReferenceFieldUpdater
AtomicIntegerFieldUpdater
,AtomicLongFieldUpdater
和AtomicReferenceFieldUpdater
都是对一个指定对象中的指定字段进行操作。这里需要注意以下几点
- 对应的字段必须非
private
修饰 - 对应的字段必须是
volatile
修饰的 -
AtomicIntegerFieldUpdater
操作的字段必须是int
类型,AtomicLongFieldUpdater
操作的字段必须是long
类型,AtomicReferenceFieldUpdater
的操作字段必须是创建AtomicReferenceFieldUpdater
对象时候指定字段的类型
这里解释一下为什么要满足上面几点。
- 因为这些类对字段进行操作都是利用反射进行操作的。
- 要保证原子性以及可见性就需要用
volatile
修饰 -
AtomicIntegerFieldUpdater
会判断操作字段是不是int
类型,同理AtomicLongFieldUpdater
会判断是不是long
类型
列举一下部分方法以及使用方式
public class AtomicXXXFieldUpdaterTest {
@Test
public void test() {
TestObject objectOne = new TestObject();
//创建一个指定对象指定字段的操作的AtomicIntegerFieldUpdater
AtomicIntegerFieldUpdater<TestObject> integerFieldUpdater = AtomicIntegerFieldUpdater.newUpdater(TestObject.class, "age");
//compareAndSet指定对象的某个int类型字段
System.out.println(integerFieldUpdater.compareAndSet(objectOne,23,22));
System.out.println(integerFieldUpdater.compareAndSet(objectOne,22,23));
//创建一个指定对象指定字段的操作的AtomicLongFieldUpdater
AtomicLongFieldUpdater<TestObject> longFieldUpdater = AtomicLongFieldUpdater.newUpdater(TestObject.class, "years");
//compareAndSet指定对象的某个long类型字段
System.out.println(longFieldUpdater.compareAndSet(objectOne,2019L,2018L));
System.out.println(longFieldUpdater.compareAndSet(objectOne,2018L,2019L));
//创建一个指定对象指定类型字段的操作的AtomicReferenceFieldUpdater
AtomicReferenceFieldUpdater<TestObject,String> referenceFieldUpdater = AtomicReferenceFieldUpdater.newUpdater(TestObject.class,String.class, "name");
//compareAndSet指定对象的指定类型字段
System.out.println(referenceFieldUpdater.compareAndSet(objectOne,"szh","acy"));
System.out.println(referenceFieldUpdater.compareAndSet(objectOne,"acy","szh"));
}
}
class TestObject{
protected volatile String name="acy";
public volatile int age=22;
protected volatile long years=2018L;
public Long getYears() {
return years;
}
public void setYears(Long years) {
this.years = years;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public Integer getAge() {
return age;
}
public void setAge(Integer age) {
this.age = age;
}
}
5. 标记操作类
5.1 AtomicMarkableReference
跟AtomicStampedReference
AtomicMarkableReference
跟AtomicStampedReference
都是标记一个类,然后根据类的引用以及标记来判断是否允许操作的类。其中AtomicMarkableReference
只能标记为true
或者false
。AtomicStampedReference
的标记只能是int
类型的,通常作为更新的版本号,也就是用来解决ABA问题。两者的实现方式相同,都是维护一个内部类分别记录对象以及版本号。简单介绍一下用法。
public class AtomicXXXFieldUpdaterTest {
@Test
public void test() {
Object objOne = new Object();
Object objTwo = new Object();
AtomicMarkableReference<Object> markableReference = new AtomicMarkableReference<>(objOne, true);
//设置标记需要reference符合
System.out.println(markableReference.attemptMark(objTwo,false));
System.out.println("设置mark需要reference符合"+markableReference.attemptMark(objOne,false));
//设置成功需要reference跟标记都是符合的
System.out.println(markableReference.compareAndSet(objTwo,objOne,false,true));
System.out.println(markableReference.compareAndSet(objTwo,objOne,true,false));
System.out.println("设置成功需要reference跟mark都是符合的-----"+markableReference.compareAndSet(objOne,objTwo,false,true));
AtomicStampedReference<Object> stampedReference = new AtomicStampedReference<>(objOne, 0);
//设置标记需要reference符合
System.out.println(stampedReference.attemptStamp(objTwo,2));
System.out.println("设置stamp需要reference符合"+stampedReference.attemptStamp(objOne,1));
//设置成功需要reference跟标记都是符合的
System.out.println(stampedReference.compareAndSet(objTwo,objOne,0,1));
System.out.println(stampedReference.compareAndSet(objTwo,objOne,1,0));
System.out.println("设置成功需要reference跟stamp都是符合的------"+stampedReference.compareAndSet(objOne,objTwo,1,0));
}
}
6.并发辅助计算类
6.1Striped64
,DoubleAccumulator
,DoubleAdder
,LongAccumulator
,LongAdder
Striped64
是一个可以在并发环境下面计数用的一个组件。这个类的思想可以参考ConcurrentHashMap
的size
方法。Striped64的设计思路是在竞争激烈的时候尽量分散竞争,在实现上,Striped64维护了一个base Count和一个Cell数组,计数线程会首先试图更新base变量,如果成功则退出计数,否则会认为当前竞争是很激烈的,那么就会通过Cell数组来分散计数,Striped64根据线程来计算哈希,然后将不同的线程分散到不同的Cell数组的index上,然后这个线程的计数内容就会保存在该Cell的位置上面,基于这种设计,最后的总计数需要结合base以及散落在Cell数组中的计数内容。这里可以参考一下这篇博文并发之Striped64。
DoubleAccumulator
,DoubleAdder
,LongAccumulator
,LongAdder
都是Striped64
的字类。主要是对数相加跟求和以及转化上的方法。这里就不多介绍了。