JAVA篇:Java 多线程 (六)原子操作类java.util.concurrent.atomic
6 原子操作类java.util.concurrent.atomic
在java.util.concurrent.atomic下有以下实现类:
6.1 非阻塞同步:CAS
6.1.1 自增程序
多线程同步的意义,在于使得多线程对变量操作的结果与预期相符,譬如说,创建20个线程,每个线程对count=0进行1000次的自增,本来预期的结果应该是20000,但是几次运行都是19134之类的结果,并且每次结果都不一致。有以下的解决方法:
-
volatile关键字修饰:保证可见性并禁止指令的重排,但是并不能保证结果与预期相符,因为依旧不是同步的。count++可以拆解为读取count,count+1,将count+1的结果写回去。当多线程并发访问时,这段代码运行时,就有可能一个线程在更新,数据还未写回,另一个线程读取了旧值,导致自增次数并不够20000次。
-
阻塞同步:即使用synchronized及Lock,线程上下文切换会耗费大量的时间
-
非阻塞同步:使用基于CAS的原子操作类
public int count = 0; class MyThread extends Thread{ @Override public void run() { for(int i=0;i<1000;i++){ count++; } } } public void test1(){ //创建20个线程自增 MyThread[] mtThreads = new MyThread[20]; for(int i=0;i<mtThreads.length;i++){ mtThreads[i] = new MyThread(); } for(int i=0;i<mtThreads.length;i++){ mtThreads[i].start(); } for(int i=0;i<mtThreads.length;i++){ try { mtThreads[i].join(); } catch (InterruptedException e) { e.printStackTrace(); } } System.out.println("操作结果是:"+count); }
6.1.2 非阻塞同步
阻塞同步和非阻塞同步都是实现线程安全的两个保障手段,非阻塞同步对于阻塞同步而言,主要解决了阻塞同步中线程阻塞和唤醒带来的性能问题。那么什么叫做非阻塞同步呢?在并发环境下某个线程对共享变量先进性操作,如果没有其他线程争用共享数据那操作就成功;如果存在数据争用冲突,那就去补偿措施,比如不断的重试机制。直到成功为止,因为这种乐观的并发策略不需要吧线程挂起,也就把这种同步操作称为非阻塞同步(操作和冲突检测具备原子性)。
在硬件指令集的发展驱动下,使得“操作和冲突检测”这种看起来需要多次操作的行为只需要一条处理器的指令便可以完成,这些指令中旧包括非常著名的CAS指令(Compare-And-Swap),《深入理解Java虚拟机第二版.周志明》第十三章中这样描述关于CAS机制。
6.2 原子操作类
6.2.1 AtomicInteger
AtomicLong的使用与AtomicInteger类似。
一个AtomicInteger用于诸如原子增量计数器的应用程序中,不能用作Integer的替代品 。 但是,这个类确实继承了Number以允许通过处理基于数字类的工具和实用程序的统一访问。
AtomicIntegerde 构造函数可传入初始值,若不传入初始值则默认为0,AtomicInteger的成员方法如下:
返回值 | 描述 |
---|---|
int |
accumulateAndGet(int x, IntBinaryOperator accumulatorFunction) 使用将给定函数应用于当前值和给定值的结果原子更新当前值,返回更新后的值。 |
int |
addAndGet(int delta) 将给定的值原子地添加到当前值。 |
boolean |
compareAndSet(int expect, int update) 如果当前值 == 为预期值,则将该值原子设置为给定的更新值。 |
int |
decrementAndGet() 原子减1当前值。 |
double |
doubleValue() 返回此值 AtomicInteger 为 double 一个宽元转换后。 |
float |
floatValue() 返回此值 AtomicInteger 为 float 一个宽元转换后。 |
int |
get() 获取当前值。 |
int |
getAndAccumulate(int x, IntBinaryOperator accumulatorFunction) 使用给定函数应用给当前值和给定值的结果原子更新当前值,返回上一个值。 |
int |
getAndAdd(int delta) 将给定的值原子地添加到当前值。 |
int |
getAndDecrement() 原子减1当前值。 |
int |
getAndIncrement() 原子上增加一个当前值。 |
int |
getAndSet(int newValue) 将原子设置为给定值并返回旧值。 |
int |
getAndUpdate(IntUnaryOperator updateFunction) 用应用给定函数的结果原子更新当前值,返回上一个值。 |
int |
incrementAndGet() 原子上增加一个当前值。 |
int |
intValue() 将 AtomicInteger 的值作为 int 。 |
void |
lazySet(int newValue) 最终设定为给定值。 |
long |
longValue() 返回此值 AtomicInteger 为 long 一个宽元转换后。 |
void |
set(int newValue) 设置为给定值。 |
String |
toString() 返回当前值的String表示形式。 |
int |
updateAndGet(IntUnaryOperator updateFunction) 使用给定函数的结果原子更新当前值,返回更新的值。 |
boolean |
weakCompareAndSet(int expect, int update) 如果当前值 == 为预期值,则将值设置为给定更新值。 |
使用AtomicInteger实现的自增程序,不过可以看到actomic下有定义累加器,在累加性能上更优,这个后面会提及。
public AtomicInteger count = new AtomicInteger(); class MyThread extends Thread{ @Override public void run() { for(int i=0;i<1000;i++){ count.incrementAndGet(); } } } public void test1(){ //创建20个线程自增 MyThread[] mtThreads = new MyThread[20]; for(int i=0;i<mtThreads.length;i++){ mtThreads[i] = new MyThread(); } for(int i=0;i<mtThreads.length;i++){ mtThreads[i].start(); } for(int i=0;i<mtThreads.length;i++){ try { mtThreads[i].join(); } catch (InterruptedException e) { e.printStackTrace(); } } System.out.println("操作结果是:"+count); }
6.2.2 AtomicBoolean
AtomicBoolean用于诸如原子更新标志的应用程序,不能用作替代Boolean 。
AtomicBoolean的构造函数默认初始值是False,可以根据传入布尔值初始化状态。其方法如下:
-
-
Modifier and Type Method and Description boolean
compareAndSet(boolean expect, boolean update)
如果当前值为==
的预期值,则将该值原子设置为给定的更新值。boolean
get()
返回当前值。boolean
getAndSet(boolean newValue)
将原子设置为给定值并返回上一个值。void
lazySet(boolean newValue)
最终设定为给定值。void
set(boolean newValue)
无条件地设置为给定的值。String
toString()
返回当前值的String表示形式。boolean
weakCompareAndSet(boolean expect, boolean update)
如果当前值为==
为预期值,则将该值原子设置为给定的更新值。
-
6.2.3 Atomic *Array
这里暂时先讲讲AtomicIntegerArray
和AtomicLongArray
,AtomicReferenceArray<E>
放在AtomicReference<V>
后面。
AtomicIntegerArray
和AtomicLongArray
类似,这里以AtomicIntegerArray
为例。
AtomicIntegerArray
可通过传入数组长度,构建给定长度并所有元素初始为0的数组,或者传入数组,将传入数组全部元素复制到AtomicIntegerArray中。
//创建给定长度的新AtomicIntegerArray,所有元素最初为零。 AtomicIntegerArray(int length) //创建一个新的AtomicIntegerArray,其长度与从给定数组复制的所有元素相同。 AtomicIntegerArray(int[] array)
在AtomicIntegerArray
中提供了指定数组索引进行更新的各种方法:如增、减、更新、设置,也提供了accumulateAndGet自定义算法更新。
Modifier and Type | Method and Description |
---|---|
int |
accumulateAndGet(int i, int x, IntBinaryOperator accumulatorFunction) 以索引 i 原子更新元素,并将给定函数应用于当前值和给定值,返回更新后的值。 |
int |
addAndGet(int i, int delta) 原子地将索引 i 的给定值添加到元素。 |
boolean |
compareAndSet(int i, int expect, int update) 如果当前值 == 为预期值,则 i 地将位置 i 处的元素设置为给定的更新值。 |
int |
decrementAndGet(int i) 索引 i 的元素原子 操作自增并返回值 。 |
int |
get(int i) 获取位置 i 的当前值。 |
int |
getAndAccumulate(int i, int x, IntBinaryOperator accumulatorFunction) 以索引 i 原子更新元素,并将给定函数应用于当前值和给定值,返回上一个值。 |
int |
getAndAdd(int i, int delta) 将索引 i 的给定值原子地添加到元素。 |
int |
getAndDecrement(int i) 索引 i 的元素原子 i 。 |
int |
getAndIncrement(int i) 在索引 i 原子上增加一个元素。 |
int |
getAndSet(int i, int newValue) 将位置 i 的元素原子设置为给定值并返回旧值。 |
int |
getAndUpdate(int i, IntUnaryOperator updateFunction) 使用应用给定函数的结果原子更新索引 i 处的元素,返回上一个值。 |
int |
incrementAndGet(int i) 在索引 i 原子上增加一个元素。 |
void |
lazySet(int i, int newValue) 最终将位置 i 的元素设置为给定值。 |
int |
length() 返回数组的长度。 |
void |
set(int i, int newValue) 将位置 i 处的元素设置为给定值。 |
String |
toString() 返回数组的当前值的String表示形式。 |
int |
updateAndGet(int i, IntUnaryOperator updateFunction) 以索引 i 原子更新应用给定函数的结果,返回更新的值。 |
boolean |
weakCompareAndSet(int i, int expect, int update) 如果当前值 == 为预期值,则 i 地将位置 i 处的元素设置为给定的更新值。 |
/*****************************`AtomicIntegerArray`*************************************************/ public void test6(){ AtomicIntegerArray atomicIntegerArray = new AtomicIntegerArray(10); System.out.println("更新前:"+atomicIntegerArray); System.out.println("创建10个线程,每个线程对索引为3的位置进行100次+3"); Runnable r = new Runnable() { @Override public void run() { for(int i=0;i<100;i++){ atomicIntegerArray.getAndAccumulate(3, 3, new IntBinaryOperator() { @Override public int applyAsInt(int left, int right) { return left+right; } }) ; } } }; Thread[] threads = new Thread[10]; for(int i=0;i<threads.length;i++){ threads[i] = new Thread(r); threads[i].start(); } for(int i=0;i<threads.length;i++){ try { threads[i].join(); } catch (InterruptedException e) { e.printStackTrace(); } } System.out.println("更新后:"+atomicIntegerArray); }
6.2.4 计算器Accumulator
DoubleAccumulator、LongAccumulator
Atomic下提供的计算器是优于其他替代方案的,是各自线程维持各自线程的计数器值,在调用get(或者其他等价的doubleValue/longValue)方法时才会进行同步。在高并发环境下,其性能提升更加明显。
在累加器,如LongAccumulator初始化时需要传入计算器函数和identity元素。Accumulator的功能更加强大,传入的计算器可以实现别的算法,比如说累乘,也可设置初始值identity。
//使用给定的累加器函数和identity元素创建一个新的实例。 LongAccumulator(LongBinaryOperator accumulatorFunction, long identity)
LongAccumulator 提供了如下方法:
Modifier and Type | Method and Description |
---|---|
void |
accumulate(long x) 具有给定值的更新。 |
double |
doubleValue() 返回 |
float |
floatValue() 返回 |
long |
get() 返回当前值。 |
long |
getThenReset() 相当于 |
int |
intValue() 在 |
long |
longValue() 相当于 |
void |
reset() 重置维持更新到标识值的变量。 |
String |
toString() 返回当前值的String表示形式。 |
-
累加器的应用循环累乘,运算结果应当是2的30次方(1073741824),也尝试过更多线程和更多运算次数,但可能是因为溢出,累乘次数过多返回0:
public void test2(){ LongAccumulator accumulator = new LongAccumulator(new LongBinaryOperator() { @Override public long applyAsLong(long left, long right) { return left*right; } },1); /* 创建10个线程,每个线程进行3次计算*2 */ Runnable runnable = new Runnable() { @Override public void run() { for(int i=0;i<3;i++){ accumulator.accumulate(2); } } }; Thread[] threads = new Thread[10]; for(int i=0;i<threads.length;i++){ threads[i] = new Thread(runnable); threads[i].start(); } for(int i=0;i<threads.length;i++){ try { threads[i].join(); } catch (InterruptedException e) { e.printStackTrace(); } } System.out.println("运算结果是:"+accumulator.get()); }
6.2.5 累加器Adder
DoubleAdder、LongAdder
Adder是Accumulator中的特例,相当于使用面的方法使用LongAccumulator.
new LongAccumulator(new LongBinaryOperator() { @Override public long applyAsLong(long left, long right) { return left+right; } },0);
Actomic下定义的加法器,一个或多个变量一起维持初始为0的值的综合。当更新(方法add)跨线程竞争时,变量及可以动态增长以减少争用,方法sum(或等效的longValue或DounbleValue)返回保持总和的整个变量组合的当前总和。
Atomic下提供的累加器和加法器都是优于其他替代方案的,尤其在高并发环境中。
在加法器,如LongAdder每次初始和为0,提供了如下基本的方法:
Modifier and Type | Method and Description |
---|---|
void |
add(long x) 添加给定值。 |
void |
decrement() 相当于 add(-1) 。 |
double |
doubleValue() 返回 |
float |
floatValue() 返回 |
void |
increment() 相当于 add(1) 。 |
int |
intValue() 在 |
long |
longValue() 相当于 |
void |
reset() 将保持总和的变量重置为零。 |
long |
sum() 返回当前总和。 |
long |
sumThenReset() 相当于 |
String |
toString() 返回 sum() 的String表示 |
-
使用累加器来实现自增:
public void test3(){ LongAdder adder = new LongAdder(); /* 创建10个线程,每个线程进行100次+1*/ Runnable runnable = new Runnable() { @Override public void run() { for(int i=0;i<1000;i++){ adder.add(1); } } }; Thread[] threads = new Thread[20]; for(int i=0;i<threads.length;i++){ threads[i] = new Thread(runnable); threads[i].start(); } for(int i=0;i<threads.length;i++){ try { threads[i].join(); } catch (InterruptedException e) { e.printStackTrace(); } } System.out.println("运算结果是:"+adder.sum()); }
6.2.6 AtomicReference<V>
AtomicReference
提供了对象引用的原子操作。按照自己的理解来说,可对非基本数据类型(如自定义类)进行原子操作,提供了许多自定义运算过程的方法。
AtomicReference
构造时的默认初始值是null,但是可通过传入参数设置初始值。它提供了如下方法
Modifier and Type | Method and Description |
---|---|
V |
accumulateAndGet(V x, BinaryOperator<V> accumulatorFunction) 使用将给定函数应用于当前值和给定值的结果原子更新当前值,返回更新后的值。 |
boolean |
compareAndSet(V expect, V update) 如果当前值 == 为预期值,则将值设置为给定的更新值。 |
V |
get() 获取当前值。 |
V |
getAndAccumulate(V x, BinaryOperator<V> accumulatorFunction) 使用给定函数应用给当前值和给定值的结果原子更新当前值,返回上一个值。 |
V |
getAndSet(V newValue) 将原子设置为给定值并返回旧值。 |
V |
getAndUpdate(UnaryOperator<V> updateFunction) 用应用给定函数的结果原子更新当前值,返回上一个值。 |
void |
lazySet(V newValue) 最终设定为给定值。 |
void |
set(V newValue) 设置为给定值。 |
String |
toString() 返回当前值的String表示形式。 |
V |
updateAndGet(UnaryOperator<V> updateFunction) 使用给定函数的结果原子更新当前值,返回更新的值。 |
boolean |
weakCompareAndSet(V expect, V update) 如果当前值为 == ,则将原值设置为给定的更新值。 |
应用方法,在代码中定义了一个类MyClass,在多线程的情况下通过AtomicReference对其value进行原子性的增加操作,最终结果是251。但是AtomicReference<MyClass>的操作主体始终是MyClass,运算的是MyClass实例之间的运算,返回的也是MyClass实例:
/*****************************AtomicReference**************************************************/ class MyClass{ private String name; private int value; public MyClass(String name,int value){ this.name = name; this.value = value; } public void setValue(int value) { this.value = value; } public int getValue() { return this.value; } public void setName(String name) { this.name = name; } public String getName() { return name; } @Override public String toString(){ return "name: "+name+",value: "+value; } } public void test4(){ //使用Integer AtomicReference<Integer> atomicReference = new AtomicReference<>(1); System.out.println(atomicReference.get()); //使用自定义类 AtomicReference<MyClass> myClassAtomicReference = new AtomicReference<MyClass>(new MyClass("myValue",1)); System.out.println(myClassAtomicReference.get()); //多线程操作:每个线程向该MyClass增加大小10的value System.out.println("初始值为0,创建5个线程,每个线程进行10次加10,结果应当是500"); Runnable runnable = new Runnable() { @Override public void run() { for(int i=0;i<10;i++){ int value = 10; //System.out.println(String.format("%s: 增加%d",Thread.currentThread().getName(),value)); myClassAtomicReference.getAndAccumulate(new MyClass("add", value), new BinaryOperator<MyClass>() { @Override public MyClass apply(MyClass myClass, MyClass myClass2) { return new MyClass("MyValue",myClass.getValue()+myClass2.getValue());; } }); } } }; Thread[] threads = new Thread[5]; for(int i=0;i<threads.length;i++) { threads[i] = new Thread(runnable); threads[i].start(); } for(int i=0;i<threads.length;i++) { try { threads[i].join(); } catch (InterruptedException e) { e.printStackTrace(); } } System.out.println("结果是:"+myClassAtomicReference.get()); }
6.2.7 AtomicStampedReference<V>
AtomicStampedReference<V>
是一个带有时间戳的对象引用,是为了解决“ABA”问题所设计。
前面我们讨论讨论原子操作的实现是自旋+CAS,然而这个又引出来另外一个问题--“ABA”问题。简单来说,线程T1,针对旧值A,想要更新到新值B,在更新过程中T2先是将值更改为C,然后再改回A,然后T1访问时检查到内存中的值依旧是A,那么CAS成功,更改为B。这个过程其实是有隐患的,最主要的问题是CAS本身是在“确定值并未被其他线程更改”的前提下,继续该线程的更改过程,但是在“ABA”问题中就打破了这个判断前提,因为值相同,却无法判断其是否更改了,“值相同<>值没有被更改过”。
“ABA”问题中一个经典的案例是针对链表实现的堆的问题,但是我看到的时候原博客已经丢失了示意图,只能自己理了理过程。
设定堆顶指针P,原堆栈为:P->A->B->C,线程T尝试更改为p->B->C,即将A出栈,中间会有线程T2进行其他更改导致了问题,具体过程如下:
以上就是由于“ABA”问题带来的隐患,各种乐观锁的实现中通常都会用版本戳version来对记录或对象标记,避免并发操作带来的问题,在Java中,AtomicStampedReference<E>
也实现了这个作用,它通过包装[E,Integer]的元组来对对象标记版本戳stamp,从而避免ABA问题,例如下面的代码分别用AtomicReference
和AtomicStampedReference
来对初始值为100的原子整型变量进行更新,AtomicReference
会成功执行CAS操作,而加上版本戳的AtomicStampedReference
对于ABA问题会执行CAS失败:
/*****************************AtomicStampedReference**************************************************/ public void test5(){ //AtomicReference AtomicReference<Integer> integerAtomicReference = new AtomicReference<>(100); AtomicStampedReference<Integer> integerAtomicStampedReference = new AtomicStampedReference<Integer>(100,0); //线程1对值进行两次交换 Runnable r1 = new Runnable() { @Override public void run() { //开始休眠,先让线程2拿到初始时间戳0 try { Thread.sleep(5); } catch (InterruptedException e) { e.printStackTrace(); } integerAtomicReference.compareAndSet(100,101); integerAtomicReference.compareAndSet(101,100); integerAtomicStampedReference.compareAndSet(100,101,integerAtomicStampedReference.getStamp(),integerAtomicStampedReference.getStamp()+1); integerAtomicStampedReference.compareAndSet(101,100,integerAtomicStampedReference.getStamp(),integerAtomicStampedReference.getStamp()+1); } }; //线程2判断值“没有改变过”则设置新值 Runnable r2 = new Runnable() { @Override public void run() { int stamp = integerAtomicStampedReference.getStamp(); //等待线程2更改两次值,形成ABA的情况 try { Thread.sleep(10); } catch (InterruptedException e) { e.printStackTrace(); } //AtomicReference通过对比值来确定“是否改变” integerAtomicReference.compareAndSet(100,202); //AtomicStampedReference通过值以及线程开始的stamp来判断“是否改变” System.out.println(String.format("%s:线程开始AtomicStampedReference的stamp:%d,现在的stamp:%d", Thread.currentThread().getName(),stamp,integerAtomicStampedReference.getStamp())); integerAtomicStampedReference.compareAndSet(100,202,stamp,stamp+1); } }; Thread t1 = new Thread(r1); Thread t2 = new Thread(r2); t1.start(); t2.start(); try { t1.join(); t2.join(); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println("AtomicReference的结果值:"+integerAtomicReference.get()); System.out.println("AtomicStampedReference的结果值:"+integerAtomicStampedReference.getReference()); }
从应用上可以看出来AtomicStampedReference
维护对象引用和一个对象更改次数的计数器stamp,如果两个有一个对不上,CAS都会失败,在构造时需要传入对象的初始实例及stamp的初始值。stamp的维护需要在代码上自己维护。
AtomicStampedReference(V initialRef, int initialStamp)
Modifier and Type | Method and Description |
---|---|
boolean |
attemptStamp(V expectedReference, int newStamp) 相当于==,判断给出值及戳记是否与内存中的相符,是则返回true,是其他函数判断的基础。 |
boolean |
compareAndSet(V expectedReference, V newReference, int expectedStamp, int newStamp) 以原子方式设置该引用和戳记给定的更新值的值,更新的前提是==的结果返回true |
V |
get(int[] stampHolder) 返回引用的戳记的当前值。 |
V |
getReference() 返回引用的当前值。 |
int |
getStamp() 返回戳记的当前值。 |
void |
set(V newReference, int newStamp) 无条件地设置引用和戳记的值。 |
boolean |
weakCompareAndSet(V expectedReference, V newReference, int expectedStamp, int newStamp) 以原子方式设置该引用和邮票给定的更新值的值,如果当前的参考是 == 至预期的参考,并且当前标志等于预期标志。 |
6.2.8 AtomicMarkableReference<V>
AtomicMarkableReference
的唯一区别就是不再用int标识引用,而是使用boolean变量——表示引用变量是否被更改过。
事实上,由于AtomicMarkableReference
的Mark仅仅存在true或false两个状态,并且这个状态还是通过代码中自己维护,并不与值绑定……所以感觉是不能解决”ABA“问题的,没有找到这个类设计出来的应用场景。其使用方法应该与AtomicStampedReference<V>
类似。
其构造函数中设置初始值和初始状态mark。
AtomicMarkableReference(V initialRef, boolean initialMark)
Modifier and Type | Method and Description |
---|---|
boolean |
attemptMark(V expectedReference, boolean newMark) :相当于==,判断引用对象的值与mark的状态是否与预期相符。 |
boolean |
compareAndSet(V expectedReference, V newReference, boolean expectedMark, boolean newMark) 以原子方式设置该引用和标记给定的更新值的值,如果当前的参考是 == 至预期的参考和当前标记等于预期标记。 |
V |
get(boolean[] markHolder) 返回引用和标记的当前值。 |
V |
getReference() 返回引用的当前值。 |
boolean |
isMarked() 返回标记的当前值。 |
void |
set(V newReference, boolean newMark) 无条件地设置引用和标记的值。 |
boolean |
weakCompareAndSet(V expectedReference, V newReference, boolean expectedMark, boolean newMark) 以原子方式设置该引用和标记给定的更新值的值,如果当前的参考是 == 至预期的参考和当前标记等于预期标记。 |
6.2.9 AtomicReferenceArray<E>
相比于AtomicIntegerArray
和AtomicLongArray
,AtomicReferenceArray<E>
并没有提供基础数据类型能做到的增减,只能进行设置,以及自定义更新过程。其使用与AtomicReference<V>
类似,不过AtomicReferenceArray<E>
对数组操作时需要指定索引位置。
初始化一样提供了两个方式:
//创建一个与原始数组相同的长度和所有元素的AtomicReferenceArray。 AtomicReferenceArray(E[] array) //创建给定长度的新AtomicReferenceArray,所有元素最初为null。 AtomicReferenceArray(int length)
Modifier and Type | Method and Description |
---|---|
E |
accumulateAndGet(int i, E x, BinaryOperator<E> accumulatorFunction) 以索引 i 原子更新元素,并将给定函数应用于当前值和给定值,返回更新后的值。 |
boolean |
compareAndSet(int i, E expect, E update) 如果当前值 == 为预期值,则 i 地将位置 i 处的元素设置为给定的更新值。 |
E |
get(int i) 获取位置 i 的当前值。 |
E |
getAndAccumulate(int i, E x, BinaryOperator<E> accumulatorFunction) 以索引 i 原子更新元素,并将给定的函数应用于当前值和给定值,返回上一个值。 |
E |
getAndSet(int i, E newValue) 将位置 i 的元素原子设置为给定值并返回旧值。 |
E |
getAndUpdate(int i, UnaryOperator<E> updateFunction) 用索引 i 原子更新元素,并使用给定函数的结果返回上一个值。 |
void |
lazySet(int i, E newValue) 最终将位置 i 的元素设置为给定值。 |
int |
length() 返回数组的长度。 |
void |
set(int i, E newValue) 将位置 i 的元素设置为给定值。 |
String |
toString() 返回数组的当前值的String表示形式。 |
E |
updateAndGet(int i, UnaryOperator<E> updateFunction) 用索引 i 原子更新应用给定函数的结果,返回更新后的值。 |
boolean |
weakCompareAndSet(int i, E expect, E update) 如果当前值 == 为预期值,则 i 地将位置 i 处的元素设置为给定的更新值。 |
/*****************************`AtomicReferenceArray<E>`*************************************************/ class MyClass2{ public String name; public int value; public MyClass2(String name,int value){ this.name = name; this.value = value; } public MyClass2 increase(){ this.value = this.value+1; return this; } @Override public String toString(){ return name+": "+value; } } public void test7(){ //初始化为null AtomicReferenceArray<MyClass2> array = new AtomicReferenceArray(6); //强制设置 array.set(0,new MyClass2("Myvalue1",0)); array.set(1,new MyClass2("Myvalue2",0)); array.set(2,new MyClass2("Myvalue3",0)); array.set(3,new MyClass2("Myvalue4",0)); array.set(4,new MyClass2("Myvalue5",0)); array.set(5,new MyClass2("Myvalue6",0)); System.out.println("更新前:"+array); System.out.println("创建10个线程,每个线程轮询100次,逐个为MyClass2的value自增,最后结果都应该是1000"); Runnable runnable = new Runnable() { @Override public void run() { for(int i=0;i<100;i++){ for(int j=0;j<array.length();j++){ array.accumulateAndGet(j, null, new BinaryOperator<MyClass2>() { @Override public MyClass2 apply(MyClass2 myClass2, MyClass2 myClass22) { return new MyClass2(myClass2.name,myClass2.value+1); } }); } } } }; Thread[] threads = new Thread[10]; for(int i=0;i<threads.length;i++){ threads[i] = new Thread(runnable); threads[i].start(); } for(int i=0;i<threads.length;i++){ try { threads[i].join(); } catch (InterruptedException e) { e.printStackTrace(); } } System.out.println("更新后:"+array); }
后面发现使用下面的方法使用,其操作并不是原子性的,之前在AtomicReference<V>
中也用错了。只要是在原MyClass2实例上修改后返回原实例的,其操作都没有实现原子性,必须使用新值构建新实例返回。
/*****************************`AtomicReferenceArray<E>错误使用方法`*************************************************/ class MyClass2{ public String name; public int value; public MyClass2(String name,int value){ this.name = name; this.value = value; } public MyClass2 increase(){ this.value = this.value+1; return this; } @Override public String toString(){ return "name: "+name+",value: "+value; } } public void test7(){ //初始化为null AtomicReferenceArray<MyClass2> array = new AtomicReferenceArray(6); //强制设置 array.set(0,new MyClass2("Myvalue1",0)); array.set(1,new MyClass2("Myvalue2",0)); array.set(2,new MyClass2("Myvalue3",0)); array.set(3,new MyClass2("Myvalue4",0)); array.set(4,new MyClass2("Myvalue5",0)); array.set(5,new MyClass2("Myvalue6",0)); System.out.println("更新前:"+array); System.out.println("创建10个线程,每个线程轮询100次,逐个为MyClass2的value自增"); Runnable runnable = new Runnable() { @Override public void run() { for(int i=0;i<100;i++){ for(int j=0;j<array.length();j++){ array.accumulateAndGet(j, null, new BinaryOperator<MyClass2>() { @Override public MyClass2 apply(MyClass2 myClass2, MyClass2 myClass22) { //错误用法:只要是在原MyClass2实例上修改后返回原实例的,其操作都没有实现原子性 return myClass2.increase(); } }); } } } }; Thread[] threads = new Thread[10]; for(int i=0;i<threads.length;i++){ threads[i] = new Thread(runnable); threads[i].start(); } for(int i=0;i<threads.length;i++){ try { threads[i].join(); } catch (InterruptedException e) { e.printStackTrace(); } } System.out.println("更新后:"+array); }
6.3 升级类型原子类
6.3.1 升级类型原子类
前面讨论的原子操作类型,包含:
-
基础类型的原子操作:
AtomicInteger
、AtomicLong
、AtomicBoolean
-
为了基本数据类型运算而封装优化的原子操作计算器:
DoubleAccumulator
、LongAccumulator
、DoubleAdder
、LongAdder
-
针对数组封装的对某索引的原子操作:
AtomicIntegerArray
、AtomicLongArray
,AtomicReferenceArray<E>
-
为了其他衍生类型定义的对象引用的原子操作:
AtomicReference<V>
-
对象引用对”ABA“问题的优化:
AtomicStampedReference<V>
、AtomicMarkableReference<V>
其中对象引用的AtomicReference<V>
和AtomicStampedReference<V>
、AtomicMarkableReference<V>
、AtomicReferenceArray<E>
都对非基本数据类型(比如说自定义类),有关数组、自定义计算操作的原子操作进行了封装,但是使用过程可以明显感觉到,这个支撑是有些”笨拙“的。譬如说,我需要对自定义类的int类型的value进行自增操作,但是我的操作并不能针对这个实例进行自增,而是每次计算都要使用value自增后的新值构建并返回一个新的实例。它将传入的对象实例视为一个操作整体。
而升级类型原子类则是提供了newUpdater的方法指定类中需要进行原子操作的名称。
6.3.2 成员方法介绍
升级类型原子类包含三类:
-
AtomicIntegerFieldUpdater<T>
:一种基于反射的实用程序,可以对指定类的指定的volatile Integer字段进行原子更新。 该类设计用于原子数据结构,其中同一节点的多个字段独立受原子更新的影响。 -
AtomicLongFieldUpdater<T>
:一种基于反射的实用程序,可以对指定类的指定的volatile long字段进行原子更新。 该类设计用于原子数据结构,其中同一节点的多个字段独立受原子更新的影响。 -
AtomicReferenceFieldUpdater<T,V>
:基于反射的实用程序,可以对指定类的指定的volatile volatile引用字段进行原子更新。 该类设计用于原子数据结构,其中同一节点的多个引用字段独立受原子更新。
这三类包含的方法与AtomicInteger
、AtomicLong
、AtomicReference<V>
类似,不过这三类都是虚拟类,也即是不能直接创建,可以选择继承并实现其虚拟方法(比较蠢),以及使用静态方法newUpdate方法构建,构建实例是需要传入指定类的类名及指定的成员变量,该成员变量必须是可访问(不能是private)的由volatile修饰的。
//AtomicIntegerFieldUpdater static <U> AtomicIntegerFieldUpdater<U> newUpdater(class<U> tclass, String fieldName) //AtomicLongFieldUpdater static <U> AtomicLongFieldUpdater<U> newUpdater(class<U> tclass, String fieldName) //AtomicReferenceFieldUpdater static <U,W> AtomicReferenceFieldUpdater<U,W> newUpdater(class<U> tclass, class<W> vclass, String fieldName)
譬如说自定义类AClass
class Myclass{ public int value; public Myclass(int value){ this.value = value; } } class AClass{ public volatile int value1; public volatile long value2; public volatile MyClass value3; public AClass(){ this.value1 = 0; this.value2 = 0; this.value3 = new Myclass(0); } }
构建三个原子操作类
//针对AClass中的value1进行操作的原子操作实例 AtomicIntegerFieldUpdater<AClass> atomicintegerAclass = AtomicIntegerFieldUpdater.newUpdater(AClass.class,"value1"); //针对AClass中的value2进行操作的原子操作实例 AtomicLongFieldUpdater<AClass> atomicLongAclass = AtomicIntegerFieldUpdater.newUpdater(AClass.class,"value2"); //针对AClass中的value3进行操作的原子操作实例 AtomicReferenceFieldUpdater<AClass,MyClass> atomicReferenceAclass = AtomicReferenceFieldUpdater.newUpdater(AClass.class,MyClass.class,"value3");
原子操作类操作AClass
AClass aclass = new Aclass(); //AtomicIntegerFieldUpdater的使用,对value1自增 atomicintegerAclass.getAndIncrement(aclass); //AtomicLongFieldUpdater的使用,对value2自增 atomicLongAclass.getAndIncrement(aclass); //AtomicReferenceFieldUpdater的使用,对Myclass value3的value进行自增 atomicReferenceAclass.accumulateAndGet(aclass, null, new BinaryOperator<MyClass>() { @Override public MyClass apply(MyClass myClass, MyClass myClass2) { return new MyClass(myClass.value+1); } });
6.3.4 应用实例:包含AtomicIntegerFieldUpdater<T>
和AtomicReferenceFieldUpdater<T,V>
如果自定义类中某个int类型成员变量需要进行原子操作,有两种方法:
-
将这个成员变量定义为
AtomicInteger
-
使用
AtomicIntegerFieldUpdater<T>
:其实例通过AtomicIntegerFieldUpdater.newUpdater构建,需要传入指定类名及指定的成员变量,该成员变量必须是可访问(不能是private)的由volatile修饰的。
/*****************************`AtomicIntegerFieldUpdater<T>*************************************************/ class MyClass{ private String name; private int value; public MyClass(String name,int value){ this.name = name; this.value = value; } public void setValue(int value) { this.value = value; } public int getValue() { return this.value; } public void setName(String name) { this.name = name; } public String getName() { return name; } @Override public String toString(){ return "name: "+name+",value: "+value; } } /* 自定义类Myclass3 */ class MyClass3{ public String name; public volatile int value;//必须是public,也必须是volatile public volatile MyClass myClass; //定义了AtomicInteger类型 public AtomicInteger value2; public MyClass3(String name,int value){ this.name = name; this.value = value; this.value2 = new AtomicInteger(0); this.myClass =new MyClass("myclass",0); } public void setValue(int value) { this.value = value; } public int getValue() { return this.value; } public void setName(String name) { this.name = name; } public String getName() { return name; } @Override public String toString(){ return name+": value-"+value+", value2-"+value2+", myclass-"+myClass; } } public void test8(){ //针对MyClass3中的value进行操作的原子操作实例 AtomicIntegerFieldUpdater<MyClass3> atomicMyclass3 = AtomicIntegerFieldUpdater.newUpdater(MyClass3.class,"value"); //创建yClass3中的myclass进行操作的原子操作实例 AtomicReferenceFieldUpdater<MyClass3,MyClass> atomicMyclass3myclass = AtomicReferenceFieldUpdater.newUpdater(MyClass3.class,MyClass.class,"myClass"); //记录自增次数 AtomicInteger integer = new AtomicInteger(0); //创建用于操作的Myclass3 MyClass3 myClass3 = new MyClass3("Myvalue",0); System.out.println("创建10个线程,每个线程对三个值进行1000次自增"); Runnable runnable = new Runnable() { @Override public void run() { for(int i=0;i<1000;i++){ //AtomicIntegerFieldUpdater的使用,对value自增 atomicMyclass3.getAndIncrement(myClass3); //AtomicReferenceFieldUpdater的使用,对myclass的value进行自增 atomicMyclass3myclass.accumulateAndGet(myClass3, null, new BinaryOperator<MyClass>() { @Override public MyClass apply(MyClass myClass, MyClass myClass2) { return new MyClass(myClass.name,myClass.value+1); } }); //对value2自增 myClass3.value2.incrementAndGet(); //计数器自增 integer.incrementAndGet(); } } }; Thread[] threads = new Thread[10]; for(int i=0;i<threads.length;i++){ threads[i] = new Thread(runnable); threads[i].start(); } for(int i=0;i<threads.length;i++){ try { threads[i].join(); } catch (InterruptedException e) { e.printStackTrace(); } } System.out.println(String.format("进行了%d次自增后,结果是%s",integer.get(),myClass3)); }
6.X 参考
什么是ABA问题? - 郁白的回答 - 知乎 https://www.zhihu.com/question/23281499/answer/120969540
0、JAVA多线程编程
Java多线程编程所涉及的知识点包含线程创建、线程同步、线程间通信、线程死锁、线程控制(挂起、停止和恢复)。之前
-
篇0
-
篇1
-
篇2
-
篇3
-
篇4
-
篇5
-
篇6
-
篇7
-
篇8