环境:
jdk1.8
摘要说明:
上一大章节主要阐述了常用的并发工具类的的使用:
本章节主要讲述原子操作CAS及常用的原子操作类;
CAS是Compare And Set的缩写,是以一种无锁的方式实现并发控制。在实际情况下,同时操作同一个对象的概率非常小,所以多数加锁操作做的是无用功,CAS以一种乐观锁的方式实现并发控制。
步骤:
1.CAS
之前有写过一个文章《高并发编程之高并发场景:秒杀(无锁、排他锁、乐观锁、redis缓存的逐步演变)》其中我们就提到使用数据库的悲观锁(排他锁)和乐观锁来实现高并发;
实际java本身也有对应悲观锁和乐观锁的实现方式:之前的synchronized关键字和后面要学习的显示锁都是悲观锁的一种实现;而CAS就是乐观锁的一种实现;
CAS是Compare And Set的缩写,是以一种无锁的方式实现并发控制。在实际情况下,同时操作同一个对象的概率非常小,所以多数加锁操作做的是无用功,CAS以一种乐观锁的方式实现并发控制。
CAS的具体实现就是给定内存中的期望值和修改后的目标值,如果实际内存中的值等于期望值,则内存值替换为目标值,否则操作失败。该操作具有原子性。
我们也可以把期望值看成版本号,就是如果修改前获取的版本号在修改时没有发生改变就进行替换;
举例:
多线程情况下如何实现count++?
使用悲观锁可以使用synchronized对变量进行加锁;
CAS的操作流程如下:
1.读取内存数据j=count;
2.CAS(j,j++);即比较内存中count数据是否还为j,如果是才进行修改;整个操作具有原子性
3.如果成功,返回;失败则重新执行第一步直到成功,也称之为自旋。
由于第二步成功的概率很大,所以采用CAS的代价很小;当高并发情况下由于CAS采用自旋的方式对CPU会有较大的操作负担,所以可能会损耗部分CPU资源。
2.原子操作类
jdk提供了许多根据CAS思路产生的原子操作类;总结下来可以分成以下几类:
- 更新基本类型类:AtomicBoolean,AtomicInteger,AtomicLong,AtomicReference
- 更新数组类:AtomicIntegerArray,AtomicLongArray,AtomicReferenceArray
- 更新引用类型:AtomicReference,AtomicMarkableReference,AtomicStampedReference
- 原子更新字段类: AtomicReferenceFieldUpdater,AtomicIntegerFieldUpdater,AtomicLongFieldUpdater
上述原子操作类都是在java.util.concurrent.atomic包下,使用起来也大同小异;首先我们看看AtomicBoolean类的使用;再举例几个典型进行使用,其他可参考api;
注:其中原子更新字段类jdk上说比其他原子类都要弱,这里就不推荐使用,大家可以使用AtomicReference代替;
AtomicBoolean:可以原子更新的布尔值。原子布尔值用于诸如原子更新标志之类的应用程序中,不能用作布尔值的替代品。
构造方法:
AtomicBoolean():创建一个初始值为false的新原子布尔值。
AtomicBoolean(boolean initialValue):创建一个指定初始值的新原子布尔值。
常用方法:
- boolean compareAndSet(boolean expect, boolean update):如果当前值==期望值,则原子化地将该值设置为给定的更新值。
- boolean get(): 获取当前值;
- boolean getAndSet(boolean newValue):原子地设置为给定值并返回前一个值。
- void lazySet(boolean newValue):最终设置为给定的值。
- void set(boolean newValue):无条件的设置成给定的值。
举例:
package pers.cc.cas;
import java.util.concurrent.atomic.AtomicBoolean;
/*
* 原子布尔类型实践
*/
public class AtomicBooleanTest {
/**
* 定义一个线程安全的Integer并设定初始值为0
*/
static AtomicBoolean atomicBoolean = new AtomicBoolean(true);
public static void main(String[] args) throws InterruptedException {
Thread successThread = new Thread(new Runnable() {
@Override
public void run() {
// 若atomicBoolean为false则自旋直到其他线程修改atomicBoolean为true;
// 这里要注意weakCompareAndSet本身是不会自旋的,就是一次性操作,修改成功或失败
while (!atomicBoolean.weakCompareAndSet(true, false)) {
System.out.println(System.currentTimeMillis()
+ "successThread:" + atomicBoolean.get());
}
System.out.println(System.currentTimeMillis() + "errorThread:"
+ atomicBoolean.get());
}
});
Thread errorThread = new Thread(new Runnable() {
@Override
public void run() {
// 若atomicBoolean为false则修改为true
atomicBoolean.compareAndSet(false, true);
System.out.println(System.currentTimeMillis() + "errorThread:"
+ atomicBoolean.get());
}
});
// 修改atomicBoolean为false
System.out.println(atomicBoolean.getAndSet(false));
successThread.start();
Thread.sleep(3);
errorThread.start();
}
}
运行结果:
true
1550569160198successThread:false
1550569160198successThread:false
......
1550569160201successThread:false
1550569160201successThread:false
1550569160201successThread:false
1550569160201errorThread:true
1550569160201successThread:false
3.AtomicInteger
AtomicInteger:可以原子更新的int值。AtomicInteger用于原子递增计数器等应用程序中,不能用作整数的替代。但是,这个类确实扩展了Number,允许处理基于数字的类的工具和实用程序进行统一访问。
构造方法:
AtomicInteger():创建一个初始值为0的新原子更新的int值。
AtomicInteger(int initialValue):创建一个指定初始值的新原子更新的int值。
常用方法:
- int accumulateAndGet(int x, IntBinaryOperator accumulatorFunction):使用将给定函数应用于当前值和给定值的结果原子地更新当前值,并返回更新后的值。
- int addAndGet(int delta):原子地将给定值添加到当前值。返回修改后的值
- boolean compareAndSet(int expect, int update):如果当前值==期望值,则原子化地将该值设置为给定的更新值。
- int decrementandget():原子性地将当前值减1。
- int get():获取当前值。
- int getandcollect (int x, IntBinaryOperator accumulatorFunction):使用将给定函数应用于当前值和给定值的结果原子地更新当前值,返回前一个值。
- int getandadd (int delta):原子地将给定值添加到当前值。
- int incrementandget():原子地将当前值增加1。
- set(int newValue):设置为给定值。
- int updateandget(IntUnaryOperator updateFunction):使用应用给定函数的结果原子地更新当前值,返回更新后的值。
实战:
package pers.cc.cas;
import java.util.concurrent.atomic.AtomicInteger;
public class AtomicIntegerTest {
/**
* 定义一个线程安全的Integer并设定初始值为0
*/
static AtomicInteger atomicInteger = new AtomicInteger(0);
static int i = 0;
public static void main(String[] args) throws InterruptedException {
for (int j = 0; j < 100000; j++) {
Thread addThread = new Thread(new Runnable() {
@Override
public void run() {
// 原子的增加1
atomicInteger.incrementAndGet();
i++;
}
});
addThread.start();
}
Thread.sleep(2000);
System.out.println(atomicInteger.get());
System.out.println(i);
}
}
运行结果是不定的i的值会不停变化,由此也可以看出AtomicInteger是一个原子操作且是线程安全的:
100000
99998
4.AtomicReference
AtomicReference:可以原子更新的对象引用
构造方法:
AtomicReference():创建一个初始值为null的新原子更新的对象。
AtomicReference(V initialValue):创建一个指定初始化的新原子更新的对象。
常用方法:
- boolean compareandset (V expect, V update):如果当前值==期望值,则原子化地将该值设置为给定的更新值。
- v get ():获取当前值。
- V getandset (V newValue):原子地设置为给定值并返回旧值。
- V getandupdate (UnaryOperator < V > updateFunction):使用应用给定函数的结果原子地更新当前值,返回前一个值。
- set(V newValue):设置为给定值。
package pers.cc.cas;
import java.util.concurrent.atomic.AtomicReference;
/**
* 可以原子更新的对象引用
*
* @author cc
*
*/
public class AtomicReferenceTest {
/**
* 创建一个原子更新的对象
*/
static AtomicReference < User > atomicReference = new AtomicReference <>();
public static void main(String[] args) {
User cc = new User("cc", 18);
/**
* 初始化对象
*/
atomicReference.set(cc);
User cccc = new User("cc", 30);
/**
* 更换对象属性
*/
atomicReference.compareAndSet(cc, cccc);
System.out.println(atomicReference.get().getAge());
System.out.println(cc.getAge());
}
static class User {
private String name;
private int age;
public User(String name, int age) {
this.name = name;
this.age = age;
}
public String getName() {
return name;
}
public int getAge() {
return age;
}
}
}
运行结果为:30,18;
可以看出AtomicReference的替换并不会更新初始对象;
5.AtomicStampedReference
AtomicStampedReference:维护一个对象引用和一个整数“戳记”,该“戳记”可以以原子方式更新。
实现注意:此实现通过创建表示“装箱”[引用、整数]对的内部对象来维护带戳记的引用。实际就是带有版本戳的更新
构造方法:
AtomicStampedReference(V initialRef, int initialStamp):使用给定的初始值创建一个新的AtomicStampedReference。。
常用方法:
- boolean attemptStamp(V expectedReference, int newStamp):如果当前引用是预期引用的==,则原子化地将戳记的值设置为给定的更新值。
- boolean compareandset (V expectedReference, V newReference, int expectedStamp, int newStamp):如果当前引用==期望的引用,并且当前戳等于期望的戳,则原子地将引用和戳记的值设置为给定的更新值。
- v get(int[]stampHolder):返回引用和戳记的当前值。
- v getreference():返回引用的当前值。
- int getstamp():返回戳记的当前值。
- void set(V newReference, int newStamp):无条件地设置引用和戳记的值。
实战:
package pers.cc.cas;
import java.util.concurrent.atomic.AtomicStampedReference;
/**
* AtomicStampedReference维护一个对象引用和一个整数“戳记”,该“戳记”可以以原子方式更新。
* 实现注意:此实现通过创建表示“装箱”[引用、整数]对的内部对象来维护带戳记的引用。
*
* @author cc
*
*/
public class AtomicStampedReferenceTest {
/**
* 定义一个原子对象,类型为String ,初始版本号为0
*/
static AtomicStampedReference < String > asr = new AtomicStampedReference <>(
"cc", 0);
public static void main(String[] args) throws InterruptedException {
/**
* 获取初始版本号
*/
final int oldStamp = asr.getStamp();
/**
* 获取初始值
*/
final String oldReferenc = asr.getReference();
System.out.println(oldReferenc + "===========" + oldStamp);
Thread rightStampThread = new Thread(new Runnable() {
@Override
public void run() {
System.out.println(Thread.currentThread().getName()
+ "当前变量值:"
+ oldReferenc
+ "当前版本戳:"
+ oldStamp
+ "-"
+ asr.compareAndSet(oldReferenc, oldReferenc + "Java",
oldStamp, oldStamp + 1));
}
});
Thread errorStampThread = new Thread(new Runnable() {
@Override
public void run() {
String reference = asr.getReference();
System.out.println(Thread.currentThread().getName()
+ "当前变量值:"
+ reference
+ "当前版本戳:"
+ asr.getStamp()
+ "-"
+ asr.compareAndSet(reference, reference + "C",
oldStamp, oldStamp + 1));
}
});
rightStampThread.start();
rightStampThread.join();
errorStampThread.start();
errorStampThread.join();
System.out.println(asr.getReference() + "===========" + asr.getStamp());
}
}
运行结果如下:
cc===========0
Thread-0当前变量值:cc当前版本戳:0-true
Thread-1当前变量值:ccJava当前版本戳:1-false
ccJava===========1
6.AtomicLongArray
AtomicLongArray:一个长数组,其中的元素可以原子地更新。
构造方法:
AtomicLongArray(int length):创建给定长度的新AtomicLongArray,所有元素初始值为0。
AtomicLongArray(long[] array):创建一个新的AtomicLongArray,其长度与给定数组相同,并且所有元素都从该数组中复制。
常用方法:
- long addandget (int i, long delta):原子地将给定的值添加到索引i处的元素。
- boolean compareandset (int i, long expect, long update):如果当前值==期望值,则原子化地将位于i位置的元素设置为给定的更新值。
- long decrementAndGet(int i):原子上,下标i的元素减少了1。
- long get(int i):获取位置i处的当前值。
- long incrementandget (int i):元素在索引i处的原子增量为1。
实战:
package pers.cc.cas;
import java.util.ArrayList;
import java.util.concurrent.atomic.AtomicLongArray;
/**
* AtomicLongArray:一个长数组,其中的元素可以原子地更新。
*
* @author cc
*
*/
public class AtomicLongArrayTest {
static long[] arr = new long[] { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9 };
/**
* 初始化一个可原子操作的数组
*/
static AtomicLongArray atomicLongArray = new AtomicLongArray(arr);
public static void main(String[] args) {
/**
* 指定游标设值
*/
atomicLongArray.set(0, 9);
System.out.println(atomicLongArray.get(0));
System.out.println(arr[0]);
/**
* 指定游标进行比较进行变更
*/
atomicLongArray.compareAndSet(1, 1, 8);
/**
* 指定游标进行减1
*/
System.out.println(atomicLongArray.decrementAndGet(9));
}
}
执行结果为:9,0.,8