当前位置 : 主页 > 编程语言 > java >

Java并发JUC——Atomic原子类

来源:互联网 收集:自由互联 发布时间:2023-02-04
什么是原子类 原子是不可分割的最小单位,故原子类可以认为其操作都是不可分割 一个操作时不可中断的,即便是在多线程的情况下也可以保证 原子类的作用和锁类似,是为了保证并

什么是原子类

  • 原子是不可分割的最小单位,故原子类可以认为其操作都是不可分割
  • 一个操作时不可中断的,即便是在多线程的情况下也可以保证
  • 原子类的作用和锁类似,是为了保证并发情况下线程安全,不过原子类相比于锁,有一定的优势
  • 粒度更细:原子变量可以把竞争范围缩小到变量级别,这是我们可以获得的最细粒度的情况了,通常锁的粒度都要大于原子变量的粒度
  • 效率更高:通常,使用原子类的效率会比使用锁的效率更高,除了高度竞争的情况

6类原子类纵览

Atomic基本原子类型

AtomicBoolean布尔型原子类、AtomicInteger整型原子类、AutomicLong长整型原子类,元老级的原子更新,方法几乎一模一样

以AtomicInteger为例

AtomicInteger常用方法

  • public final int get():获取当前的值
  • public final int getAndSet(int newValue):获取当前的值,并设置新值
  • public final int getAndIncrement():获取当前的值,并自增
  • public final int getAndDecrement():获取当前的值,并自减
  • public final int getAndAdd(int delta):获取当前的值,并加上预期的值
  • public final boolean compareAndSet(int expect, int update):如果输入的值等于预期的值,则以原子方式将该值设置为输入的值(update)
/** * @Description: 演示AtomicInteger的基本用法,对比非原子类的线程安全问题, * 使用了原子类之后不需要加锁,也可以保证线程安全 */ public class AtomicIntegerDemo1 implements Runnable { public static final AtomicInteger atomicInteger = new AtomicInteger(); public void incrementAtomic(){ atomicInteger.getAndIncrement(); } public static volatile int basicCount = 0; /** * 普通变量必须要加锁才能保证线程安全问题 */ public synchronized void incrementBasic(){ basicCount++; } @Override public void run() { for (int i = 0; i < 10000; i++) { incrementAtomic(); incrementBasic(); } } public static void main(String[] args) throws InterruptedException { AtomicIntegerDemo1 atomicIntegerDemo1 = new AtomicIntegerDemo1(); Thread thread1 = new Thread(atomicIntegerDemo1); Thread thread2 = new Thread(atomicIntegerDemo1); thread1.start(); thread2.start(); thread1.join(); thread2.join(); System.out.println("原子类的结果:"+atomicInteger.get()); System.out.println("普通变量的结果:"+basicCount); } }

原子更新数组

通过原子的方式更新数组里的某个元素,Atomic包提供了以下三个类:

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

AtomicIntegerArray类主要是提供原子的方式更新数组里的整型,其常用方法如下

  • int addAndGet(int i, int delta):以原子方式将输入值与数组中索引i的元素相加。
  • boolean compareAndSet(int i, int expect, int update):如果当前值等于预期值,则以原子方式将数组位置i的元素设置成update值。

AtomicIntegerArray的使用

/** * @Description: 演示原子数组的使用方法 */ public class AtomicArrayDemo1 { public static void main(String[] args) throws InterruptedException { AtomicIntegerArray atomicIntegerArray = new AtomicIntegerArray(1000); Thread[] threads1 = new Thread[100]; Thread[] threads2 = new Thread[100]; Decrement decrement = new Decrement(atomicIntegerArray); Increment increment = new Increment(atomicIntegerArray); for (int i = 0; i < 100; i++) { threads1[i] = new Thread(decrement); threads2[i] = new Thread(increment); threads1[i].start(); threads2[i].start(); } for (int i = 0; i < 100; i++) { threads1[i].join(); threads2[i].join(); } for (int i = 0; i < atomicIntegerArray.length(); i++) { if(atomicIntegerArray.get(i) != 0){ System.out.println("发现了错误:错误的索引:"+i); } } System.out.println("运行结束"); } } class Decrement implements Runnable{ private AtomicIntegerArray array; public 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; public Increment(AtomicIntegerArray array) { this.array = array; } @Override public void run() { for (int i = 0; i < array.length(); i++) { array.getAndIncrement(i); } } }

AtomicIntegerArray类需要注意的是,数组value通过构造方法传递进去,然后AtomicIntegerArray会将当前数组复制一份,所以当AtomicIntegerArray对内部的数组元素进行修改时,不会影响到传入的数组。

原子更新引用类型

原子更新基本类型的AtomicInteger,只能更新一个变量,如果要原子的更新多个变量,就需要使用这个原子更新引用类型提供的类。Atomic包提供了以下三个类:

  • AtomicReference:原子更新引用类型。
  • AtomicReferenceFieldUpdater:原子更新引用类型里的字段。
  • AtomicMarkableReference:原子更新带有标记位的引用类型。可以原子的更新一个布尔类型的标记位和引用类型。构造方法是AtomicMarkableReference(V initialRef, boolean initialMark)

AtomicReference引用类型原子类

  • AtomicReference:AtomicReference类的作用,和AtomicInteger并没有本质区别,AtomicInteger可以让一个整数保证原子性,AtomicReference可以让一个对象保证原子性,当然,AtomicReference的功能明显比AtomicInteger强,因为一个对象里可以包含很多属性,其用法和AtomicInteger类似

AtomicReference的使用

public class SpinLock { private AtomicReference<Thread> sign = new AtomicReference<>(); public void lock(){ Thread current = Thread.currentThread(); while(!sign.compareAndSet(null,current)){ System.out.println(Thread.currentThread().getName() + "自旋锁获取失败,再次尝试"); } } public void unlock(){ Thread current = Thread.currentThread(); sign.compareAndSet(current,null); } public static void main(String[] args) { SpinLock spinLock = new SpinLock(); Runnable runnable = new Runnable() { @Override public void run() { System.out.println(Thread.currentThread().getName() + "尝试获取自旋锁"); spinLock.lock(); System.out.println(Thread.currentThread().getName() + "获取到了自旋锁"); try { Thread.sleep(1000); } catch (InterruptedException e) { e.printStackTrace(); } finally { spinLock.unlock(); System.out.println(Thread.currentThread().getName() + "释放了自旋锁"); } } }; Thread thread1 = new Thread(runnable); Thread thread2 = new Thread(runnable); thread1.start(); thread2.start(); } }

原子更新字段类

如果我们只需要更新某个类里的某个字段,那么就需要使用原子更新字段类,Atomic包提供了以下三个类:

  • AtomicIntegerFieldUpdater:原子更新整型的字段的更新器。
  • AtomicLongFieldUpdater:原子更新长整型字段的更新器。
  • AtomicStampedReference:原子更新带有版本号的引用类型。该类将整数值与引用关联起来,可用于原子的更数据和数据的版本号,可以解决使用CAS进行原子更新时,可能出现的ABA问题。
  • 原子更新字段类都是抽象类,每次使用都时候必须使用静态方法newUpdater创建一个更新器。原子更新类的字段的必须使用public volatile修饰符。

使用上述类是必须遵循以下原则:

  • 字段必须是voliatile类型的,在线程之间共享变量时能保持可见性。
  • 字段的描述类型是与调用者的操作对象字段保持一致。
  • 也就是说调用者可以直接操作对象字段,那么就可以反射进行原子操作。
  • 对父类的字段,子类不能直接操作的,尽管子类可以访问父类的字段
  • 只能是实例变量,不能是类变量,也就是说不能加static关键字
  • 只能是可修改变量,不能使用final修饰变量,final的语义,不可更改。

AtomicIntegerFieldUpdater

  • AtomicIntegerFieldUpdater:对普通变量进行升级,让其拥有原子操作能力
  • 使用场景:偶尔需要一个原子get-set操作

AtomicIntegerFieldUpdater注意点

  • 可以修改的变量具有可见性,即volatile修饰的变量
  • 该变量不可以被static修饰

AtomicIntegerFieldUpdater使用

/** * @Description: 演示AtomicIntegerFieldUpdater用法 */ public class AtomicIntegerFieldUpdaterDemo implements Runnable { static Candidate tom; static Candidate peter; public static AtomicIntegerFieldUpdater<Candidate> scoreUpdater = AtomicIntegerFieldUpdater.newUpdater(Candidate.class,"score"); @Override public void run() { for (int i = 0; i < 10000; i++) { peter.score++; scoreUpdater.getAndIncrement(tom); } } public static void main(String[] args) throws InterruptedException { tom = new Candidate(); peter = new Candidate(); AtomicIntegerFieldUpdaterDemo updaterDemo = new AtomicIntegerFieldUpdaterDemo(); Thread thread1 = new Thread(updaterDemo); Thread thread2 = new Thread(updaterDemo); thread1.start(); thread2.start(); thread1.join(); thread2.join(); System.out.println("普通变量自增:"+peter.score); System.out.println("AtomicIntegerFieldUpdater操作的变量:"+tom.score); } public static class Candidate{ volatile int score; } }

高性能原子类

高性能原子类,是java8中增加的原子类,它们使用分段的思想,把不同的线程hash到不同的段上去更新,最后再把这些段的值相加得到最终的值,这些类主要有:

  • 1、Striped64:下面四个类的父类。
  • 2、LongAccumulator:long类型的聚合器,需要传入一个long类型的二元操作,可以用来计算各种聚合操作,包括加乘等。
  • 3、LongAdder:long类型的累加器,LongAccumulator的特例,只能用来计算加法,且从0开始计算。
  • 4、DoubleAccumulator:double类型的聚合器,需要传入一个double类型的二元操作,可以用来计算各种聚合操作,包括加乘等。
  • 5、DoubleAdder:double类型的累加器,DoubleAccumulator的特例,只能用来计算加法,且从0开始计算。
  • 这几个类的操作基本类似,其中DoubleAccumulator和DoubleAdder底层其实也是用long来实现的

Adder累加器

  • 高并发下LongAdder比AtomicLong效率高,不过本质是空间换时间
  • 在竞争激烈的时候,LongAdder把不同线程对应到不同的Cell上进行修改,降低了冲突的概率,是多段锁的理念,提高了并发性
  • AtomicLong中有个内部变量value保存着实际的long值,所有的操作都是针对该变量进行。也就是说,高并发环境下,value变量其实是一个热点,也就是N个线程竞争一个热点。由于竞争很激烈,每一次操作,都要flush和refresh,导致很多资源浪费
  • LongAdder的基本思路就是分散热点,将value值分散到一个数组中,不同线程会命中到数组的不同槽中,各个线程只对自己槽中的那个值进行CAS操作,这样热点就被分散了,冲突的概率就小很多。如果要获取真正的long值,只要将各个槽中的变量值累加返回。

演示高并发场景下LongAdder比AtomicLong性能好

/** * @Description: 演示高并发场景下LongAdder比AtomicLong性能好 */ public class AtomicLongDemo { public static void main(String[] args) throws InterruptedException { AtomicLong counter = new AtomicLong(0); ExecutorService executorService = Executors.newFixedThreadPool(20); long start = System.currentTimeMillis(); for (int i = 0; i < 10000; i++) { executorService.submit(new Task(counter)); } executorService.shutdown(); while(!executorService.isTerminated()){ } long end = System.currentTimeMillis(); System.out.println(counter.get()); System.out.println("AtomicLong耗时:"+(end - start)); } static class Task implements Runnable{ private AtomicLong counter; public Task(AtomicLong counter) { this.counter = counter; } @Override public void run() { for (int i = 0; i < 10000; i++) { counter.incrementAndGet(); } } } } /** * @Description: 演示高并发场景下LongAdder比AtomicLong性能好 */ public class LongAdderDemo { public static void main(String[] args) throws InterruptedException { LongAdder counter = new LongAdder(); ExecutorService executorService = Executors.newFixedThreadPool(20); long start = System.currentTimeMillis(); for (int i = 0; i < 10000; i++) { executorService.submit(new Task(counter)); } executorService.shutdown(); while(!executorService.isTerminated()){ } long end = System.currentTimeMillis(); System.out.println(counter.sum()); System.out.println("LongAdder耗时:"+(end - start)); } static class Task implements Runnable{ private LongAdder counter; public Task(LongAdder counter) { this.counter = counter; } @Override public void run() { for (int i = 0; i < 10000; i++) { counter.increment(); } } } }

LongAdder比AtomicLong对比

  • 在低征用下,AtomicLong和LongAdder这两个类具有相似的特征,但在竞争激烈的情况下,LongAdder的预期吞吐量要高得多,但要消耗更多的空间
  • LongAdder适合的场景是统计求和计数的场景,而且LongAdder基本只提供了add等方法,而AtomicLong还具有CAS方法

Accumulator累加器

  • Accumulator和Adder非常相似,Accumulator就是一个更通用版本的Adder

LongAccumulator的用法

/** * @Description: 演示LongAccumulator的用法 * */ public class LongAccumulatorDemo { public static void main(String[] args) { LongAccumulator longAccumulator = new LongAccumulator((x,y) -> x+y,0); ExecutorService executorService = Executors.newFixedThreadPool(8); IntStream.range(1,10).forEach(i -> executorService.submit(() -> longAccumulator.accumulate(i))); executorService.shutdown(); while(!executorService.isTerminated()){ } System.out.println(longAccumulator.getThenReset()); demo2(); } public static void demo1(){ LongAccumulator longAccumulator = new LongAccumulator((x,y) -> x+y,0); longAccumulator.accumulate(1); longAccumulator.accumulate(2); System.out.println(longAccumulator.getThenReset()); } public static void demo2(){ LongAccumulator longAccumulator = new LongAccumulator((x,y) -> Math.max(x,y),0); ExecutorService executorService = Executors.newFixedThreadPool(8); IntStream.range(1,10).forEach(i -> executorService.submit(() -> longAccumulator.accumulate(i))); executorService.shutdown(); while(!executorService.isTerminated()){ } System.out.println(longAccumulator.getThenReset()); } }

LongAccumulator类原理探究

LongAdder类是LongAccumulator的一个特例,LongAccumulator提供了比LongAdder更强大的功能,如下构造函数其中accumulatorFunction一个双目运算器接口,根据输入的两个参数返回一个计算值,identity则是LongAccumulator累加器的初始值。

public LongAccumulator(LongBinaryOperator accumulatorFunction, long identity) { this.function = accumulatorFunction; base = this.identity = identity; } @FunctionalInterface public interface LongBinaryOperator { /** * Applies this operator to the given operands. * * @param left the first operand * @param right the second operand * @return the operator result */ //根据两个参数计算返回一个值 long applyAsLong(long left, long right); }

LongAccumulator相比于LongAdder,可以为累加器提供非0的初始值,而LongAdder只能提供默认的0值。 另外,LongAccumulator还可以指定累加规则,比如累加或者相乘,只需要在构造LongAccumulator时,传入自定义的双目运算器即可,后者则内置累加规则。

LongAddr与LongAccumulator类相同点?

  • LongAddr与LongAccumulator类都是使用非阻塞算法CAS实现的,这相比于使用锁实现原子性操作在性能上有很大的提高。
  • LongAddr类是LongAccumulator类的一个特例,只是LongAccumulator提供了更强大的功能,可以让用户自定义计算规则。

参考: https://ifeve.com/java-atomic/

https://www.cnblogs.com/tong-yuan/p/Atomic.html

https://www.cnblogs.com/tong-yuan/p/LongAdder.html

https://cloud.tencent.com/developer/article/1466107

https://segmentfault.com/a/1190000015865714

上一篇:Java并发JUC——CAS原理
下一篇:没有了
网友评论