Loading

JUC原子类

JUC原子类

CAS

实现线程安全的方法:

  • 互斥同步:synchronized,Lock.
  • 非阻塞同步:CAS,Atomic.
  • 无同步方法:栈封闭,ThreadLocal,可重入代码

CAS介绍

  • CAS(Compare-And-Swap):对比并交换,是一个原子操作.
  • 先比较旧值是否发生变化,若没有,则交换成新值;否则不进行交换.
  • CAS是原子的,多线程使用CAS更新数据时,可以不使用锁.

示例:

public AtomicInteger i = new AtomicInteger(0);

public int add(){
    return i.addAndGet(1);
}

存在的问题:

  • ABA问题:CAS只检查值是否发生变化,若一个值由A变为B后又变回为A,则认为没有变化而正常进行操作.
  • 循环时间长开销大:自旋CAS若长时间不成功,则会给CPU带来很大的执行开销.
  • 只能保证一个共享变量的原子操作:当对一个共享变量进行操作时,可以使用CAS保证原子操作.但是要需要对多个共享变量进行操作,则无法保证多个变量的原子性.

解决:

  • ABA问题:使用版本号,在变量上加入版本号,每次变量变更后更新对应的版本号,若版本号一致才认为没有改变.(JUC提供AtomicStampedReference,其先检查当前引用是否等于预期引用,若等于才设置为新值)
  • 自旋问题:使用处理器提供的pause指令==>延迟流水线执行指令,避免循环时因内存顺序冲突引起的流水线被清空.
  • 多变量问题:使用AtomicReference类将多个变量放在一个对象中进行CAS操作.

UnSafe类

提供的功能:

  • 内存操作
  • CAS
  • Class相关
  • 对象操作
  • 线程调度
  • 系统信息获取
  • 内存屏障
  • 数组操作

CAS原理:使用自旋调用UnSafe中CAS更新,若失败则重试.


AtomicInteger

常用API

public final int get(); // 获取值
public final int getAndSet(int newValue); // 获取当前值,并设为新值
public final int getAndIncrement(); // 获取旧值,并自增
public final int getAndDecrement(); // 获取旧值,并自增
public final int getAndAdd(int delta); // 获取旧值,并加指定值
void lazySet(int newValue); // 设为指定值,但非即时

原理:使用volatile和CAS保证原子操作.

  • volatile:保证可见性,多线程并发时,一个线程修改,其他线程可见.
  • CAS:保证原子性.

原子类小结

原子更新基本类型

  • AtomicBoolean:原子更新布尔类型.
  • AtomicInteger:原子更新整型.
  • AtomicLong:原子更新长整型.

原子更新数组

  • AtomicIntegerArray:原子更新整型数组.
  • AtomicLongArray:原子更新长整型数组.
  • AtomicReferenceArray:原子更新引用类型数组.
    • get(int index):获取指定位置上的元素.
    • compareAndSet(int i,E except,E update):若当前值等于预期值,则原子方式设置为新值.

原子更新引用类型

  • AtomicReference:原子更新引用类型.
  • AtomicStampedReference:原子更新引用类型,使用Pair存储元素值和版本号.
  • AtomicMarkableReference:原子更新带有标记的引用类型.

原子更新字段类

  • AtomicIntegerFieldUpdater:原子更新整型的字段的更新器.
  • AtomicLongFieldUpdater:原子更新长整型的字段的更新器.
  • AtomicReferenceFieldUpdater:原子更新引用类型字段的更新器.

示例:

// 1. 由AtomicIntegerFieldUpdater的newUpdater获取更新器(传入对应的类和待更新的字段名)
// 2. 使用getAndAdd方法,传入待更新的对象实例和更新值
// 注: 更新的字段类型要和更新器一致,否则抛出异常
//      字段必须是volatile,保证线程间的可见性
//      字段的修饰符要和操作对象的字段类型一致
//      只能操作实例对象的字段,不能操作static字段
//      不能修改final字段,不可对不可变字段更新
//      Integer/Long包装类要使用AtomicReferenceUpdater进行修改
public class Test{
    public void test(){
        newClass nc = new newClass();
        AtomicIntegerFieldUpdater<newClass> updater1 = AtomicIntegerFieldUpdater.newUpdater(newClass.class, "publicVal");
        int res1 = updater1.getAndAdd(nc, 2);
        System.out.println(res1);
        System.out.println(nc.publicVal);

        // 更新器类型与更新字段类型不一致,抛出异常
        AtomicIntegerFieldUpdater<newClass> updater2 = AtomicIntegerFieldUpdater.newUpdater(newClass.class, "integerVal");
        int res2 = updater2.getAndAdd(nc, 1);
        System.out.println(res2);
        System.out.println(nc.integerVal);
    }
}

class newClass{
    public volatile int publicVal = 1;
    protected volatile int protectedVal = 2;
    private volatile int privateVal = 3;
    public volatile static int staticVal = 4;
    public volatile Integer integerVal = 5;
    public volatile Long longVal = 6l;
}

AtomicStampedReference原理

解决ABA问题

静态私有类Pair包含两个域:

  • reference:维护对象引用.
  • stamp:标志版本.

更新流程:

  • 若元素值和版本号没有改变,且更新值不变,返回true.
  • 若元素值和版本号未变,而更新值不同,则构造新的Pair对象并进行CAS更新Pair.

小结:

  • 使用版本号控制.
  • 不重复使用Pair的引用,每次新建Pair作为CAS对象.

示例:

private static AtomicStampedReference stamp = new AtomicStampedReference(0,0); // 初始的reference和stamp

    @Test
    public void test() throws InterruptedException {
        Thread t1 = new Thread(() -> {
            try {
                Thread.sleep(200);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            boolean isSuccess = stamp.compareAndSet(0, 1, 0, 1);
            System.out.println(isSuccess);
        });

        // 线程2的stamp不匹配,无法设置成功
        Thread t2 = new Thread(() -> {
            try {
                Thread.sleep(100);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            boolean isSuccess = stamp.compareAndSet(0, 2, 1, 2); // 若设为(0,2,0,2)则有可能更新成功
            System.out.println(isSuccess);
        });

        t1.start();
        t2.start();
        TimeUnit.SECONDS.sleep(1);
        System.out.println("reference is: " + stamp.getReference() + " stamp: "+stamp.getStamp());
    }

参考:

posted @ 2020-06-30 15:29  战五渣渣渣渣渣  阅读(124)  评论(0编辑  收藏  举报