原子类

简介

Java从JDK 1.5开始提供了java.util.concurrent.atomic包,这个包中的原子操作类提供了一种用法简单、性能高效、线程安全地更新一个变量的方式。在atomic包里一共提供了17个类(JDK8,支持32bit最高的版本,目前还会继续维持下去),属于4种类型的原子更新方式,分别是原子更新基本类型、原子更新引用类型、原子更新属性、原子更新数组
atomic包里的类基本都是使用Unsafe实现的包装类,而Unsafe类提供 了如下3个CAS方法。这三个方法都带有4个参数,这些参数依次指代:对象、成员变量、期望的值、更新的值。

  • compareAndSwapInt(Object var1, long var2, int var4, int var5)
  • compareAndSwapLong(Object var1, long var2, long var4, long var6)
  • compareAndSwapObject(Object var1, long var2, Object var4, Object var5)

long指成员变量位于内存中的偏移量(有方法,调用一下就可以了)

public final native boolean compareAndSwapObject(Object var1, long var2, Object var4, Object var5);

    public final native boolean compareAndSwapInt(Object var1, long var2, int var4, int var5);

    public final native boolean compareAndSwapLong(Object var1, long var2, long var4, long var6);

都是native

我看一下我要改的值是不是我期望得值,是就替换,不是就失败。竞争小的时候可以用原子类,竞争大的失败率比较高,不太适合原子类,适合加锁

volite对读写操作是原子的,其他的运算操作不能保证原子。而actomic里面提供了一些方法保证运算操作是原子的。

i++不是原子的,那怎么把它变成原子呢?

1、加锁

2、用AtomiclInteger、 AtomicLong(轻量级的原子类)(如果是引用类型的就用AtomicReference)

01 /原子更新基本类型

包括3个类: AtomicInteger、 AtomicLong、 AtomicBoolean, 这3个类提供的方法几乎模一样。
AtomicInteger源码:
以AtomicInteger为例,它包含如下常用的方法: getAndAdd()返回旧值. addAndGet()返回新值. getAndIncrement()加1,incrementAndGet(),compareAndSet()原子替换值等 。

public final boolean compareAndSet(int expect, int update) {
        return unsafe.compareAndSwapInt(this, valueOffset, expect, update);
    }


public final int getAndAdd(int delta) {
        return unsafe.getAndAddInt(this, valueOffset, delta);
    }


 public final int incrementAndGet() {
        return unsafe.getAndAddInt(this, valueOffset, 1) + 1;
    }


public final boolean compareAndSet(int expect, int update) {
        return unsafe.compareAndSwapInt(this, valueOffset, expect, update);
    }
....

Unsafe类的实现源码:
上述方法底层均利用Unsafe来类实现,涉及到的方法主要有getIntVolatile(), compareAndSwaplnt()等,这些方法均为本地实现代码,通过本地操作系统底层的API实现原子操作。

其他基本类型的原子操作:
其他基本类型的变量,如char、float、 double, 可以先转换为整型,然后再进行原子操作。例如,AtomicBoolean就是先把Boolean转换成整型,再使用compareAndSwaplnt进行CAS操作。

02 /原子更新引用类型

包括3个类:

  • AtomicReference:原子更新引用类型;
  • AtomicStampedReference: 原子更新带有标记位(整数)的引用类型;
  • AtomicMarkableReference: 原子更新带有标记位(布尔)的引用类型。
    其中,后两个类用于解决ABA问题,区别是标记位的类型不同,它们分别以整数/布尔类型做标记位。

从A到B再到A,cas检测不到变换了,对于基本类型忽略,对于引用类型加标记,

当pair(引用和版本)(引用不变,版本号不变)就替换乘新的pair

AtomicStampedReference
     
//封装pair 引用和版本
 private static class Pair<T> {
        final T reference;
        final int stamp;
        private Pair(T reference, int stamp) {
            this.reference = reference;
            this.stamp = stamp;
        }
        static <T> Pair<T> of(T reference, int stamp) {
            return new Pair<T>(reference, stamp);
        }
    }

//比较和替换pair
public boolean compareAndSet(V   expectedReference,
                                 V   newReference,
                                 int expectedStamp,
                                 int newStamp) {
        Pair<V> current = pair;
        return
            expectedReference == current.reference &&
            expectedStamp == current.stamp &&
            ((newReference == current.reference &&
              newStamp == current.stamp) ||
             casPair(current, Pair.of(newReference, newStamp)));
    }

  private boolean casPair(Pair<V> cmp, Pair<V> val) {
        return UNSAFE.compareAndSwapObject(this, pairOffset, cmp, val);
    }
03 /原子更新属性

包括3个类: AtomiclIntegerFieldUpdater、AtomicLongFieldUpdater、AtomicReferenceFieldUpdater。
如果一个类是自己编写的,则可以在编写的时候把成员变量定义为Atomic类型。但如果是-个已经有的类,在不能更改其源代码的情况下,要想实现对其成员变量的原子操作,就需要使用上述三个类。将我要使用的传给这个类,让他去给我做原子更新操作(相当于一个工具类)

java全量更新sql java增量更新_java

04 /原子更新数组

包括3个类: AtomicIntegerArray、 AtomicLongArray、 AtomicReferenceArray, 可以通过原子的方式更新数组里的某个元素。

原子替换数组中的元素:求i个元素的偏移量,提高位移运算,提高性能

05 / Striped64

从JDK 8开始, Java提供 了Striped64类,该类共包含4个子类,分别是LongAdder、LongAccumulator、DoubleAdder、DoubleAccumulator。 之所以提供这些类,是为了解决在高并发场景下多个线程对同一个变量进行CAS操作的性能问题。

并发高,每次都自旋怎么办?

加锁,或者LongAdder(一个long拆分为若干段,最后再算总和)

longadder怎么实现的?分多份,怎么分多份?

继承Striped64类,LongAdder类中使用方法add()和longValue()实现分多份相加

Striped64
 transient volatile long base;  // transient序列化对象时,他不会被序列化,因为不能序列化它的局部。

longAccumulate()  //creating new Cells, and/or contention
  //对cell做原子的操作,  创建新的cells,统一cell多大,怎么累加进去
LongAdder:每次都是累加累加
add();//采用cas的方法将x累加到不同的部分
//完整值怎么取?
longValue();
LongAccumulator: 
    function:自定义二元操作(加减乘除)
    identity:初始值
    accumulate();//原子运算

即LongAdder,LongAccumulator都是继承Striped64都是将一个long拆解为若干份,前者运算符为+,后者自定义二元运算符

06 /原子操作三大问题

ABA问题:

如果一个值原来是A,变成了B,又变成了A.那么使用CAS进行检查时会发现它的值没有发生变化,但是实际上却变化了。ABA问题的解决思路就是使用版本号,在变量前面追加上版本号,每次变量更新的时候把版本号加1,那么A->B->A就会 变成1A->2B->3A. atomic包 里提供了AtomicStampedReference(每次更新版本号+1)和AtomicMarkableReference(版本号为boolean型)类来解决ABA问题。

自旋开销大:

CAS操作通常要伴随自旋(写个死循环去cas尝试),而自旋CAS如果长时间不成功,会给CPU带来非常大的执行开销。可以先自旋几圈,如果依然拿不到锁再进行阻塞,以这样的方式来降低自旋的开销,synchronized就是这样的实现策略。

只能保证一个变量的原子操作:

对多个共享变量操作时,循环CAS就无法保证操作的原子性。这个时候可以把多个共享变量合并成一个共享变量,然后采用原子更新引用类型的方式来操作,也可以采用加锁的方式来解决问题。

原子类比较轻量级不用阻塞,但也带来了一些问题。不行就加锁

原子类底层用的是Unsafe类的那三个cas方法去实现的,那三个方法是操作系统给我们提供的原子操作。

对多个共享变量操作时,循环CAS就无法保证操作的原子性。这个时候可以把多个共享变量合并成一个共享变量,然后采用原子更新引用类型的方式来操作,也可以采用加锁的方式来解决问题。

原子类比较轻量级不用阻塞,但也带来了一些问题。不行就加锁

原子类底层用的是Unsafe类的那三个cas方法去实现的,那三个方法是操作系统给我们提供的原子操作。