最近参加了公司的王者荣耀比赛,一直开黑都忘记整理知识了,肥宅肥宅,希望能拿到一部Iphone 11 pro max(虽然根本不可能)
Atomic原子类
JUC并发包提供一系列的原子性操作,这些类都是使用非阻塞算法CAS实现的,相比使用锁实现原子性操作这在性能上有很大 的提高。
JUC并发包中含有AtomicInteger、AtomicLong和AtomicBoolean等原子操作类。以AtomicLong为例。
public class AtomicLong extends Number implements java.io.Serializable {
private static final long serialVersionUID = 1927816293512124184L;
// (1)获取Unsafe实例
private static final Unsafe unsafe = Unsafe.getUnsafe();
//(2)存放变量value的偏移量
private static final long valueOffset;
//(3)判断JVM是否支持Long类型无所CAS
static final boolean VM_SUPPORTS_LONG_CAS = VMSupportsCS8();
/**
* Returns whether underlying JVM supports lockless CompareAndSet
* for longs. Called only once and cached in VM_SUPPORTS_LONG_CAS.
*/
private static native boolean VMSupportsCS8();
static {
try {
//(4)获取value在AtomicLong中的偏移量
valueOffset = unsafe.objectFieldOffset
(AtomicLong.class.getDeclaredField("value"));
} catch (Exception ex) { throw new Error(ex); }
}
//(5)实际变量值
private volatile long value;
public AtomicLong(long initialValue) {
value = initialValue;
}
...
}
- 代码(1)中通过Unsafe.getUnsafe()方法获取到Unsafe的实例,之所以能通过Unsafe.getUnsafe获取到Unsafe的实例,这是因为 AtomicLong类也是在rt.jar包下面的,AtomicLong类就是通过Bootstarp类加载器进行加载的。
- 代码(5)中的value被声明为volatile的,这是为了多线程下保证内存可见性,value是具体存放计数的变量。
- 代码(2)(4)获取value变量在AtomicLong类中的偏移量。
//调用unsafe方法,原子性设置value值为原始值+1,返回值为递增后的值
public final long incrementAndGet() {
return unsafe.getAndAddLong(this, valueOffset, 1L) + 1L;
}
//调用unsafe方法,原子性设置value值为原始值-1,返回值为递减之后的值
public final long decrementAndGet() {
return unsafe.getAndAddLong(this, valueOffset, -1L) - 1L;
}
//调用unsafe方法,原子性设置value原始值+1,返回原始值
public final long getAndIncrement() {
return unsafe.getAndAddLong(this, valueOffset, 1L);
}
//调用unsafe方法,原子性设置value为原始值-1,返回值为原始值
public final long getAndDecrement() {
return unsafe.getAndAddLong(this, valueOffset, -1L);
}
- 如上代码中都是通过调用Unsafe的getAndAddLong方法来实现操作,这个函数是原子性操作,这里第一个参数是AtomicLong实例的引用,第二个参数是value变量在AtomicLong中的偏移值,第三个参数是要设置的第二个变量的值。
public final boolean compareAndSet(long expect, long update) {
return unsafe.compareAndSwapLong(this, valueOffset, expect, update);
}
- 在如上代码中在内部还是调用了unsafe.compareAndSwapLong方法,如果原子变量中的value值等于expect,则使用update值更新该值并返回true,否则返回false。
public class AtomicTest {
private static AtomicLong atomicLong = new AtomicLong();
private static Integer[] arrOne = new Integer[]{0,1,2,3,4,5,6};
private static Integer[] arrTwo = new Integer[]{10,12,34,43,2,5,2};
public static void main(String[] args) throws InterruptedException {
Thread threadOne = new Thread(new Runnable() {
@Override
public void run() {
int size = arrOne.length;
for (int i = 0;i<size;i++){
if (arrOne[i].intValue() == 0){
atomicLong.incrementAndGet();
}
}
}
});
Thread threadTwo = new Thread(new Runnable() {
@Override
public void run() {
int size = arrTwo.length;
for (int i=0;i<size;i++){
if (arrTwo[i].intValue() == 0){
atomicLong.incrementAndGet();
}
}
}
});
//启动子线程
threadOne.start();
threadTwo.start();
//等待线程执行完毕
threadOne.join();
threadTwo.join();
System.out.println("count 0 "+atomicLong.get());
}
}
- 如上代码中两个线程各自统计自己所持数据中0的个数,每当找到一个0就会调用AtomicLong的原子性递增方法。
- 在没有原子类的情况下,实现计数器需要使用一定的同步措施,比如使用synchronized关键字等,这些都是阻塞算法,对性能有一定的损耗,原子操作类使用非阻塞算法CAS,性能更好。但是在高并发情况下AtomicLong还会存在性能问题,因此JDK8提供了一个高并发下性能更好的LongAdder类。
- AtomicLong通过CAS提供非阻塞的原子性操作,相比使用阻塞算法的同步器在性能方面有了提升,但是在高并发下大量线程会同时去竞争同一个原子变量,但是由于同时只有一个线程的CAS会操作成功,这就造成大量线程竞争失败,会通过无线循环不断进行自旋尝试CAS操作,这样会浪费CPU资源。
在JDK8中新增LongAdder类,实现结构如下:
- 在使用LongAdder时,内部会维护多个Cell变量,每个Cell变量中有一个初始值为0的long型变量,这样在同等并发量的情况下,争夺单个变量更新操作的线程量会减少,这变相的减少了争夺共享资源的并发量。另外多个线程在争夺同一个Cell原子变量时如果失败了,它并不是一直在当前Cell变量上自旋CAS重试,而是尝试在其它Cell变量上进行尝试,这个改变增加了当前线程重试CAS成功的可能性。最后在获取LongAdder当前值时,把所有的Cell变量的value值累加后再加上base返回。
LongAdder维护一个延迟初始化的原子性更新数组(默认情况下Cell数组是null)和一个基值变量base。由于Cell占用的内存相对比较大,所以一开始并不创建它,而是在需要时创建,也就是惰性加载。 - 当一开始判断Cell数组是null并且并发线程较少时,所有的累加操作都会对base变量进行操作。保持Cell数组的大小为2的N次方,在初始化时Cell数组中的Cell元素个数为2,数组里面的变量实体是Cell类型,Cell类型是AtomicLong的一个改进,用来减少缓存的竞争,也就是解决伪共享问题。
LongAdder代码分析
为了解决高并发下多线程对一个变量CAS争夺失败后进行自旋而造成的降低并发性能问题,LongAdder内部维护多个Cell元素来分担对单个变量进行争夺的开销?
LongAdder的结构是怎样的?
当前线程应该访问Cell数组里面的哪个元素?
如何初始化Cell数组?
Cell数组如何扩容?
线程访问分配的Cell元素有冲突后如何处理?
如何保证线程操作被分配的Cell元素的原子性?
- 由该构造图可知,LongAdder类继承自Striped64类,在Striped64内部维护三个变量,LongAdder的真实值其实是base的值与Cell数组里面所有Cell元素的value值的累加,base是个基础值,默认为0。cellBusy用来实现自旋锁,状态只有0和1,当创建Cell元素,扩容Cell数组或者初始化Cell数组时,使用CAS操作该变量来保证同时只有一个线程可以进行其中之一的操作。
接下来看Cell源码:
@sun.misc.Contended static final class Cell {
volatile long value;
Cell(long x) { value = x; }
final boolean cas(long cmp, long val) {
return UNSAFE.compareAndSwapLong(this, valueOffset, cmp, val);
}
// Unsafe mechanics
private static final sun.misc.Unsafe UNSAFE;
private static final long valueOffset;
static {
try {
UNSAFE = sun.misc.Unsafe.getUnsafe();
Class<?> ak = Cell.class;
valueOffset = UNSAFE.objectFieldOffset
(ak.getDeclaredField("value"));
} catch (Exception e) {
throw new Error(e);
}
}
}
- 从源码中可以看到Cell中内部维护了一个被声明的volatile变量,这里声明为volatile是因为线程操作value变量时没有使用锁,为了保证变量的内存可见性,这里将其声明为volatile的。另外CAS函数通过CAS操作保证当前线程更新时被分配的Cell元素中value值的原子性,另外Cell类使用@sun.misc.Contended修饰是为了避免伪共享 。
在LongAdder中的源码:
long sum()
public long sum() {
Cell[] as = cells; Cell a;
long sum = base;
if (as != null) {
for (int i = 0; i < as.length; ++i) {
if ((a = as[i]) != null)
sum += a.value;
}
}
return sum;
}
- long
sum是返回当前的值,内部操作是累加所有Cell内部的value值后在家base。在以上代码中并没有对Cell数组进行加锁,所以在累加过程中可能有其它线程对Cell中的值进行了修改,也有可能对数组进行了扩容,所以sum值返回的值并不是非常精确的,其返回值并不是一个调用sum方法时的原子快照。
void reset()
public void reset() {
Cell[] as = cells; Cell a;
base = 0L;
if (as != null) {
for (int i = 0; i < as.length; ++i) {
if ((a = as[i]) != null)
a.value = 0L;
}
}
}
- reset方法为重置操作,如上代码中会把base值为0,如果Cell数组有元素,则元素值会被重置为0.
long sumThenReset()
public long sumThenReset() {
Cell[] as = cells; Cell a;
long sum = base;
base = 0L;
if (as != null) {
for (int i = 0; i < as.length; ++i) {
if ((a = as[i]) != null) {
sum += a.value;
a.value = 0L;
}
}
}
return sum;
}
- 该方法时sum的一个改造版本,如上代码在使用sum累加对应的Cell值后,把当前Cell的值重置为0,base重置为0,这样多线程调用该方法时会有问题。
void add(long x)
public void add(long x) {
Cell[] as; long b, v; int m; Cell a;
if ((as = cells) != null || !casBase(b = base, b + x)) {//(1)
boolean uncontended = true;
if (as == null || (m = as.length - 1) < 0 ||//(2)
(a = as[getProbe() & m]) == null ||//(3)
!(uncontended = a.cas(v = a.value, v + x)))//(4)
longAccumulate(x, null, uncontended);//(5)
}
}
- 在以上方法中,(1)会首先判断Cell是否为null,如果为null则在当前基础变量base上进行累加
- 如果Cell不为null或者执行代码(1)的CAS失败了,则会去执行代码(2)。代码(2)(3)决定当前线程应该访问Cell数组里面的哪个元素,如果当前线程映射的元素存在则执行代码(4),使用CAS操作去更新分配的Cell元素的value值,如果当前线程映射的元素不存在或者存在但是CAS操作失败则执行代码(5)。其实将代码(2)(3)(4)合起来看就是获取当前线程应该访问的Cell数组的Cell元素。
- 另外当前线程访问Cell数组的哪个Cell元素是通过getProbe()&m进行计算的,其中m是当前Cell数组元素个数-1,getProbe()则用于获取当前线程中变量threadLocalRandomProbe的值,这个值一开始为0,在代码(5)中会进行初始化。并且当前线程通过分配的Cell元素的CAS函数来保证对Cell元素的value值更新的原子性。