Atomic 原子类
1. 原子类介绍
-
不可分割的
-
一个操作是不可中断的,即使多线程的情况下也可以保证, 即使是在多个线程一起执行的时候,一个操作一旦开始,就不会被其他线程干扰。
-
原子类的作用和锁类似,是为了保证并发情况下线程安全,不过相比于锁,更有优势
优势: 粒度更细,效率更高
原子类纵览:
类型 | Value |
---|---|
Atomic*基本类型原子类 | AtomicInteger AtomicLong AtomicBoolean |
Atomic*Arrays数组类型原子类 | AtomicIntegerArray AtomicLongArray AtomicReferenceArray |
Atomic*Reference引用类型原子类 | AtomicReference AtomicStampedReference AtomicMarkableReference |
Atomic*Fieldupdate升级类型原子类 | AtomicIntegerFieldUpdater AtomicLongFieldUpdater AtomicReferenceFieldUpdater |
Adder累加器 | LongAdder DoubleAdder |
Accumulator累加器 | LongAccumulator DoubleAccumulator |
2. 基本类型原子类
- AtomicInteger :整型原子类
- AtomicLong :长整型原子类
- AtomicBoolean :布尔型原子类
以 AtomicInteger 为例
AtomicInteger 类常用方法 :
public final int get() //获取值
public final void set(int newValue) // 设置值
public final void lazySet(int newValue) //最终设置为给定的值
public final int getAndSet(int newValue) // 获取当前值,并设置新值
public final int getAndIncrement() //获取当前值 并自增
public final int getAndDecrement() //获取当前的值,并自减
public final int getAndAdd(int delta) // 获取当前值,并加上预期值
public final int getAndAdd(int delta) // 获取当前值,并加上预期值
public final boolean compareAndSet(int expect, int update) //比较并替换
使用 :
import java.util.concurrent.atomic.AtomicInteger;
public class AtomicIntegerTest {
public static void main(String[] args) {
int temvalue = 0;
AtomicInteger i = new AtomicInteger(0);
temvalue = i.getAndSet(3);
System.out.println("temvalue:" + temvalue + "; i:" + i);//temvalue:0; i:3
temvalue = i.getAndIncrement();
System.out.println("temvalue:" + temvalue + "; i:" + i);//temvalue:3; i:4
temvalue = i.getAndAdd(5);
System.out.println("temvalue:" + temvalue + "; i:" + i);//temvalue:4; i:9
}
}
案例 :
public class Test {
public static void main(String[] args) {
TestDemo thread = new TestDemo();
Thread t1 = new Thread(thread,"窗口一");
Thread t2 = new Thread(thread,"窗口二");
t1.start();
t2.start();
}
}
class TestDemo implements Runnable{
//共享的火车票变量
private int count = 100;
//重寫run方法
@Override
public void run() {
while (count > 0){
try {
//休眠一下 方便出现并发问题
Thread.sleep(50);
}catch (Exception e){
e.getMessage();
}
sale();
}
}
//卖票
public void sale(){
if(count > 0){
System.out.println(Thread.currentThread().getName() +"出售 :" +(100 - count + 1));
count--;
}
}
}
窗口一出售 :37
窗口二出售 :39
窗口一出售 :40
窗口二出售 :41
窗口一出售 :41
窗口一出售 :43
窗口二出售 :43
窗口一出售 :45
窗口二出售 :45
窗口一出售 :47
窗口二出售 :47
多线程下会出现重复卖票的情况,我们解决这个问题可以使用 JDK 内置锁 (Synchronized)保证线程原子性,当某个线程取到锁后,其他线程就会等待,但是性能低下,我们可以使用AtomicInteger类,是一个专门提供可以保证原子性的类
package com.dimple.test;
import java.util.concurrent.atomic.AtomicInteger;
public class Test5 {
public static void main(String[] args) {
TestDemo thread = new TestDemo();
Thread t1 = new Thread(thread,"窗口一");
Thread t2 = new Thread(thread,"窗口二");
t1.start();
t2.start();
}
}
class TestDemo implements Runnable{
//共享的火车票变量
private static AtomicInteger atomic = new AtomicInteger(100);
//重寫run方法
@Override
public void run() {
while (atomic.get() > 0){
try {
//休眠一下 方便出现并发问题
Thread.sleep(50);
}catch (Exception e){
e.getMessage();
}
sale();
}
}
//卖票
public void sale(){
if(atomic.get() > 0){
Integer count= 100 - atomic.getAndDecrement() + 1; //使用底层方法getAndDecrement() 自-1;
System.out.println(Thread.currentThread().getName()+ "," + count);//获取当前值
}
}
}
窗口一出售 :91
窗口二出售 :92
窗口一出售 :93
窗口二出售 :94
窗口一出售 :95
窗口二出售 :96
窗口一出售 :97
窗口二出售 :98
窗口一出售 :99
窗口二出售 :100
原理分析 :
// setup to use Unsafe.compareAndSwapInt for updates
private static final Unsafe unsafe = Unsafe.getUnsafe();
private static final long valueOffset;
static {
try {
valueOffset = unsafe.objectFieldOffset
(AtomicInteger.class.getDeclaredField("value"));
} catch (Exception ex) { throw new Error(ex); }
}
private volatile int value;
public AtomicInteger(int initialValue) {
value = initialValue;
}
- AtomicInteger 类主要利用 CAS(compare and swap) + volatile 的 native 方法来保证原子操作,从而避免 synchronized 的高开销,执行效率大为提升。
- CAS(compare and swap) 的原理是那期望值和原本的一个值作比较,如果相同则更新成新的值, UnSafe 类的 objectFieldOffset() 方法是一个本地方法,这个方法是用来拿到 “原来的值” 的内存地址。并且 value 是一个 volatile变量,在内存中可见,因此 JVM可以保证任何时刻线程总能拿到该变量的最新值。
3. 数组类型原子类
- AtomicIntegerArray :整形数组原子类
- AtomicLongArray :长整形数组原子类
- AtomicReferenceArray :引用类型数组原子类
以 AtomicIntegerArray 为例
AtomicIntegerArray 类常用方法
public final int get(int i) //获取值
public final void set(int i, int newValue) // 设置值
public final void lazySet(int i, int newValue) //最终集的元素在位置 i到给定值。
public final int getAndSet(int i, int newValue) //自动设置元素的位置 i到给定值并返回旧值。
public final boolean compareAndSet(int i, int expect, int update) //自动设置元素的位置 i给更新后的值,如果预期值 ==期望值。
public final int getAndIncrement(int i) //自动递增一个指数 i元素。
public final int getAndDecrement(int i) //自动递减指数 i元素
使用 :
import java.util.concurrent.atomic.AtomicIntegerArray;
public class AtomicIntegerArrayTest {
public static void main(String[] args) {
int temvalue = 0;
int[] nums = { 1, 2, 3, 4, 5, 6 };
AtomicIntegerArray i = new AtomicIntegerArray(nums);
for (int j = 0; j < nums.length; j++) {
System.out.println(i.get(j));
}
temvalue = i.getAndSet(0, 2);
System.out.println("temvalue:" + temvalue + "; i:" + i);
temvalue = i.getAndIncrement(0);
System.out.println("temvalue:" + temvalue + "; i:" + i);
temvalue = i.getAndAdd(0, 5);
System.out.println("temvalue:" + temvalue + "; i:" + i);
}
}
案例 :
public class AtmoicArray {
public static void main(String[] args) throws InterruptedException {
AtomicIntegerArray atomicIntegerArray=new AtomicIntegerArray(1000);
Decrement decrement = new Decrement(atomicIntegerArray);
Increment increment = new Increment(atomicIntegerArray);
Thread[] threads= new Thread[100];
Thread[] threads2= new Thread[100];
for (int i = 0; i < 100 ; i++) {
threads2[i]=new Thread(decrement);
threads[i]=new Thread(increment);
threads2[i].start();
threads[i].start();
}
for (int i = 0; i < 100 ; i++) {
threads2[i].join();
threads[i].join();
}
for (int i = 0; i < atomicIntegerArray.length(); i++) {
if(atomicIntegerArray.get(i)!=0) {
System.out.println("发现非0值" + i);
}
}
System.out.println("运行结束");
}
}
class Decrement implements Runnable{
private AtomicIntegerArray array;
Decrement(AtomicIntegerArray array) {
this.array = array;
}
@Override
public void run() {
for (int i = 0; i < array.length() ; i++) {
array.getAndDecrement(i);
}
}
}
class Increment implements Runnable{
private AtomicIntegerArray array;
Increment(AtomicIntegerArray array) {
this.array = array;
}
@Override
public void run() {
for (int i = 0; i < array.length() ; i++) {
array.getAndIncrement(i);
}
}
}
运行结束
运行结果 ,会发现我们数组线程每次 加100 减100 ,并不会出现不等于0的数据,数据并没有出现错乱,AtomicIntegerArray 给我们提供了数组的原子性
原理分析 :
private static final Unsafe unsafe = Unsafe.getUnsafe();
private static final int base = unsafe.arrayBaseOffset(int[].class);
private static final int shift;
private final int[] array;
static {
int scale = unsafe.arrayIndexScale(int[].class);
if ((scale & (scale - 1)) != 0)
throw new Error("data type scale not a power of two");
shift = 31 - Integer.numberOfLeadingZeros(scale);
}
private long checkedByteOffset(int i) {
if (i < 0 || i >= array.length)
throw new IndexOutOfBoundsException("index " + i);
return byteOffset(i);
}
private static long byteOffset(int i) {
return ((long) i << shift) + base;
}
- unsafe.arrayBaseOffset 获取该类型的数组,在对象存储时,存放第一个元素的内存地址,相对于数组对象起始的内存偏移量,unsafe.arrayIndexSacle(int[].class) 获取该类型的数组中元素的大小,占用多少个字节
- 根据scale ,base 定位到任意一个下标的地址 举例 : int scale = 4;1个int类型,在java中占用4个字节, Integer.numberOfLeadingZeros(scale); 返回 scale 高位连续0的个数,得出shift = 2, 而shift在如下方法使用,shift就是用来定位数组中的内存位置,用来移位
4. 引用类型原子类
基本类型原子类只能更新一个变量,如果需要原子更新多个变量,需要使用引用类型原子类
- AtomicReference :引用类型原子类
- AtomicStampedReference :原子更新带有版本号的引用类型
- AtomicMarkableReference :原子更新带有标记的引用类型
AtomicReference 常用方法
public final V get() //获取值
public final void set(V newValue) //设置值
public final void lazySet(V newValue) //最终设置为给定的值
public final boolean compareAndSet(V expect, V update) //自动设置的值来指定更新值
public final V getAndSet(V newValue) //自动设置为给定的值并返回旧值。
public final V getAndUpdate(UnaryOperator<V> updateFunction) //自动更新当前值与结果应用给定的函数,返回前一个值。
AtomicReference 使用
public class Test {
public static void main(String[] args) {
AtomicReference<Person> atomicReference = new AtomicReference<Person>();
Person person = new Person("abc", 22);
atomicReference.set(person);
Person updatePerson = new Person("Daisy", 20);
atomicReference.compareAndSet(person, updatePerson);
System.out.println(atomicReference.get().getName());
System.out.println(atomicReference.get().getAge());
}
}
@Data
class Person {
private String name;
private int age;
}
Daisy
20
AtomicStampedReference 使用
public class Test {
public static void main(String[] args) {
// 实例化、取当前值和 stamp 值
final Integer initialRef = 0, initialStamp = 0;
final AtomicStampedReference<Integer> asr = new AtomicStampedReference<Integer>(initialRef, initialStamp);
System.out.println("currentValue=" + asr.getReference() + ", currentStamp=" + asr.getStamp());
// compare and set
final Integer newReference = 666, newStamp = 999;
final boolean casResult = asr.compareAndSet(initialRef, newReference, initialStamp, newStamp);
System.out.println("currentValue=" + asr.getReference()
+ ", currentStamp=" + asr.getStamp()
+ ", casResult=" + casResult);
// 获取当前的值和当前的 stamp 值
int[] arr = new int[1];
final Integer currentValue = asr.get(arr);
final int currentStamp = arr[0];
System.out.println("currentValue=" + currentValue + ", currentStamp=" + currentStamp);
// 单独设置 stamp 值
final boolean attemptStampResult = asr.attemptStamp(newReference, 88);
System.out.println("currentValue=" + asr.getReference()
+ ", currentStamp=" + asr.getStamp()
+ ", attemptStampResult=" + attemptStampResult);
// 重新设置当前值和 stamp 值
asr.set(initialRef, initialStamp);
System.out.println("currentValue=" + asr.getReference() + ", currentStamp=" + asr.getStamp());
}
currentValue=0, currentStamp=0
currentValue=666, currentStamp=999, casResult=true
currentValue=666, currentStamp=999
currentValue=666, currentStamp=88, attemptStampResult=true
currentValue=0, currentStamp=0
AtomicMarkableReference 使用
public class Test {
public static void main(String[] args) {
// 实例化、取当前值和 mark 值
final Boolean initialRef = null, initialMark = false;
final AtomicMarkableReference<Boolean> amr = new AtomicMarkableReference<Boolean>(initialRef, initialMark);
System.out.println("currentValue=" + amr.getReference() + ", currentMark=" + amr.isMarked());
// compare and set
final Boolean newReference1 = true, newMark1 = true;
final boolean casResult = amr.compareAndSet(initialRef, newReference1, initialMark, newMark1);
System.out.println("currentValue=" + amr.getReference()
+ ", currentMark=" + amr.isMarked()
+ ", casResult=" + casResult);
// 获取当前的值和当前的 mark 值
boolean[] arr = new boolean[1];
final Boolean currentValue = amr.get(arr);
final boolean currentMark = arr[0];
System.out.println("currentValue=" + currentValue + ", currentMark=" + currentMark);
// 单独设置 mark 值
final boolean attemptMarkResult = amr.attemptMark(newReference1, false);
System.out.println("currentValue=" + amr.getReference()
+ ", currentMark=" + amr.isMarked()
+ ", attemptMarkResult=" + attemptMarkResult);
// 重新设置当前值和 mark 值
amr.set(initialRef, initialMark);
System.out.println("currentValue=" + amr.getReference() + ", currentMark=" + amr.isMarked());
}
currentValue=null, currentMark=false
currentValue=true, currentMark=true, casResult=true
currentValue=true, currentMark=true
currentValue=true, currentMark=false, attemptMarkResult=true
currentValue=null, currentMark=false
5. 升级类型原子类
- AtomicIntegerFieldUpdater : 原子更新整形字段的更新器
- AtomicLongFieldUpdater :原子更新长整形字段的更新器
- AtomicReferenceFieldUpdater :原子更新引用类型里的字段的更新器
AtomicIntegerFieldUpdater 常用方法
public final V get() //获取值
public final void set(V newValue) //设置值
public final void lazySet(V newValue) //最终设置为给定的值
public final boolean compareAndSet(V expect, V update) //自动设置的值来指定更新值
public final V getAndSet(V newValue) //自动设置为给定的值并返回旧值。
public final V getAndUpdate(UnaryOperator<V> updateFunction) //自动更新当前值与结果应用给定的函数,返回前一个值。
public final V getAndAccumulate(T obj, V x,
BinaryOperator<V> accumulatorFunction) //自动更新与应用给出的函数的值与给定值的结果指标 i元素,返回前一个值。
public final V accumulateAndGet(T obj, V x,BinaryOperator<V> accumulatorFunction) //自动更新与应用给出的函数的值与给定值的结果指标 i元素,返回更新后的值。
AtomicIntegerFieldUpdater 使用
public class Test {
public static void main(String[] args) {
AtomicIntegerFieldUpdater<User> a = AtomicIntegerFieldUpdater.newUpdater(User.class, "age");
User user = new User("Java", 22);
System.out.println(a.getAndIncrement(user));// 22
System.out.println(a.get(user));// 23
}
}
@Data
class User {
private String name;
public volatile int age;
}
22
23
要想原子地更新对象的属性需要两步。第一步,因为对象的属性修改类型原子类都是抽象类,所以每次使用都必须使用静态方法 newUpdater()创建一个更新器,并且需要设置想要更新的类和属性。第二步,更新的对象属性必须使用 public volatile 修饰符。
6. Adder 累加器
- 是JDK 1.8 中 引入的一个比较新的类
- 高并发的情况下 LongAdder 比 AtomitLong 效率高,不过是空间换时间
- 竞争激烈的时候,LingAdder 把不同的线程对应到不同的cell 上修改,降低了冲突的概率,是多段锁的理念,提高了并发性
测试 AtomicLong的性能
import java.util.concurrent.Executors;
import java.util.concurrent.atomic.AtomicLong;
/**
* 演示高并发情况下LongAdder 比 AtomicLong
* 性能好
*/
public class AtomicLongDemo {
public static void main(String[] args) throws InterruptedException {
AtomicLong atomicLong = new AtomicLong(0);
//线程池开始时间
long start = System.currentTimeMillis();
ExecutorService executorService = Executors.newFixedThreadPool(20);
for (int i = 0; i < 10000; i++) {
executorService.submit(new Task(atomicLong));
}
//表示线程池执行完毕
executorService.shutdown();
while (!executorService.isTerminated()){
}
long end = System.currentTimeMillis();
System.out.println(atomicLong.get());
System.out.println("耗时"+(end -start));
}
public static class Task implements Runnable{
private AtomicLong atomicLong;
public Task(AtomicLong atomicLong) {
this.atomicLong = atomicLong;
}
@Override
public void run() {
for (int i = 0; i < 10000; i++) {
atomicLong.incrementAndGet();
}
}
}
}
测试 LongAdder 性能
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.atomic.AtomicLong;
import java.util.concurrent.atomic.LongAdder;
/**
* 演示高并发情况下LongAdder 比 AtomicLong
* 性能好
*/
public class LongAdderDemo {
public static void main(String[] args) throws InterruptedException {
LongAdder atomicLong = new LongAdder();
//线程池开始时间
long start = System.currentTimeMillis();
ExecutorService executorService = Executors.newFixedThreadPool(20);
for (int i = 0; i < 10000; i++) {
executorService.submit(new Task(atomicLong));
}
//表示线程池执行完毕
executorService.shutdown();
while (!executorService.isTerminated()){
}
long end = System.currentTimeMillis();
System.out.println(atomicLong.sum() );
System.out.println("耗时"+(end -start));
}
public static class Task implements Runnable{
private LongAdder longAdder;
public Task(LongAdder longAdder) {
this.longAdder = longAdder;
}
@Override
public void run() {
for (int i = 0; i < 10000; i++) {
longAdder.increment();
}
}
}
}
会发现 LongAdder 比 AtomicLong 快了好多
- 他们内部实现有些不同,AtomicLong每次加法都需要同步,所以冲突比较多,也就降低了效率
- 而 LongAdder ,每个线程都有自己的计数器,仅用来线程计数,不会和其他线程打扰
- AtomicLong引入了分段锁的概念,内部有一个base变量 和 cell[] 数组共同参与计数
- base变量:竞争不激烈,直接累加到该变量上
- cell [] 数组: 竞争激烈,各个线程分累加到自己到cell[i] 卡槽中
7. Accumulator 累加器
- Accumualtor 和 Adder 非常相似,Accumualtor就是更通用的版本Adder
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.atomic.LongAccumulator;
import java.util.stream.IntStream;
public class LongAccumulatorDemo {
public static void main(String[] args) {
LongAccumulator accumulator = new LongAccumulator((x,y)->x+y,0);
ExecutorService executorService = Executors.newFixedThreadPool(8);
IntStream.range(1,10).forEach(i->executorService.submit(()->accumulator.accumulate(i)));
executorService.shutdown();
while (!executorService.isTerminated())
System.out.println(accumulator.getThenReset());
}
45
个人博客地址:http://blog.yanxiaolong.cn | 『纵有疾风起,人生不言弃』