Java-原子操作类

学习《Java并发编程之美》第4章后,简要记录一下几个原子操作类的语法和原理。

各种原子操作类内部通过Unsafe使用CAS实现,保证多线程变量的安全性,相比使用锁实现原子性操作,在性能上有很大提高。常用类有AtomicLong、AtomicInteger、AtomicBoolean等

一、AtomicLong

1.初始化

public AtomicLong(long initialValue);若不设值,默认为0

2.自增自减

先增减后返回:incrementAndGet()和decrementAndGet()

先返回值后增减:getAndIncrement()和getAndDecrement()

3.增量超过1的,减法将参数变为负数即可

public final long addAndGet(long delta)

public final long getAndAdd(long delta)

4.乘除

atomicLong.set(atomicLong.get()*6);

看起来不像是原子操作,调用get()前的一瞬间被改动的话,会覆盖掉其他线程的数值

5.其他的几个类例如AtomicInteger、AtomicBoolean使用方法都差不多

二、LongAdder

AtomicLong通过CAS提供的非阻塞的原子性操作,但是大量线程去争抢更新同一个原子变量,只有一个线程成功,其他线程会无限循环不断进行自旋尝试,浪费CPU资源。

JDK8新增的原子性递增或者递减类LongAdder类就是处理这个问题的,将一个原子变量分解成多个变量,线程获取其中一个即可增减,减少争夺共享资源的并发量。

1.LongAdder的结构

Cell数组就是分解后的多个变量,供线程操作;

base相当于原来的原子变量,初始为0,由Cell数组累加而成;

cellsBusy用来实现自旋锁,状态值为0或1;

2.当前线程会访问数组哪个变量?

源码中有as[getProbe() & m]获取下标,m=数组长度-1,getProbe()用于获取当前线程中变量threadLocalRandomProbe的值,这个值叫探针变量,不同线程不同;

3.如何初始化Cell数组?

并发量较少时,Cell数组没有初始化,只调用casBase函数对base变量进行CAS累加;

并发量逐渐增多时,casBase函数会失败,如果Cell数组为null或者为空,调用longAccumlate函数进行初始化;

4.如何保证被分配到的Cell元素的原子性?

用@sun.misc.Contended注解修饰变量避免伪共享

(1)什么是伪共享?

高速缓冲存储器是为了解决主内存和CPU运行速度差的问题而存在的中介桥梁,一般被继承道CPU内部,也叫CPU Cache。内部按行存储,行是Cache与主内存进行数据交换的单位,大小为2的幂次方字节。

同一行有多个变量的话,线程会先找缓存再找主内存。例如缓存行存有x和y两个变量,线程A和B分别从主内存拿到x和y到自己的缓存区,A对x做出修改,y没变。B只需要y但是因为x被改动了,B要重新去主存读取数据。对主存造成压力。

(2)如何避免伪共享?

一个缓存行只存一个变量,行内其他字节用其他变量填充;或者用@sun.misc.Contended注解修饰变量,自动填充;

5.常用方法

(1)long sum(); 计算LongAdder对象的值,base+Cell数组总和。计算过程没有对Cell数组加锁,期间可能有其他线程进行修改或者扩容,求和结果不精准;

(2)void reset(); base和Cell数组全部置为0;

(3)sumThenReset(); sum()+reset(),求和过程中置空;

(4)add(long x); 加一个值x,想要减值则将x设置为负数; 

 

posted @ 2021-06-15 21:18  守林鸟  阅读(285)  评论(0编辑  收藏  举报