JUC——原子类

1.基本类型原子操作类

  • AtomicInteger:整型原子类

  • AtomicBoolean:布尔型原子类

  • AtomicLong:长整型原子类

1.1常用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) //获取当前的值,并加上预期的值
boolean compareAndSet(int expect, int update) //如果输入的数值等于预期值,则以原子方式将该值设置为输入值(update)
public final void lazySet(int newValue)//最终设置为newValue,使用 lazySet 设置之后可能导致其他线程在之后的一小段时间内还是可以读到旧的值。

2.数组类型原子操作类

  • AtomicIntegerArray:整型数组原子类

  • AtomicLongArray:长整型数组原子类

  • AtomicRefereceArray:引用类型数组原子类

2.1常用API

public final int get(int i) //获取 index=i 位置元素的值
public final int getAndSet(int i,int newValue)//返回index=i位置的当前的值,并将其设置为新值:newValue
public final int getAndIncrement(int i)//获取 index=i 位置元素的值,并让该位置的元素自增
public final int getAndDecrement(int i) //获取 index=i 位置元素的值,并让该位置的元素自减
public final int getAndAdd(int i, int delta) //获取 index=i 位置元素的值,并加上预期的值
boolean compareAndSet(int i, int expect, int update) //如果输入的数值等于预期值,则以原子方式将 index=i 位置的元素值设置为输入值(update)
public final void lazySet(int i, int newValue)//最终 将index=i 位置的元素设置为newValue,使用 lazySet 设置之后可能导致其他线程在之后的一小段时间内还是可以读到旧的值。

3.引用类型原子操作类

  • AtomicReference:引用类型原子类

  • AtomicStampedReference:原子更新带有版本号,可解决CAS中的ABA问题,记录的是修改过几次。

  • AtomicMarkableReference:原子更新带有标记(false/true),记录的是是否被修改过。

3.1AtomicMarkableReference的使用

public class Test4 {
    static AtomicMarkableReference markableReference = new AtomicMarkableReference(100, false);

    public static void main(String[] args) {
        new Thread(() -> {
            boolean marked = markableReference.isMarked();
            System.out.println(Thread.currentThread().getName() + "\t" + "默认标识: " + marked);//t1	默认标识: false
            try {
                TimeUnit.SECONDS.sleep(1);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            markableReference.compareAndSet(100, 1000, marked, !marked); //t1进行修改 mark->true

        }, "t1").start();

        new Thread(() -> {
            boolean marked = markableReference.isMarked();
            System.out.println(Thread.currentThread().getName() + "\t" + "默认标识: " + marked);//t2	默认标识: false
            try {
                TimeUnit.SECONDS.sleep(2);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            boolean b = markableReference.compareAndSet(100, 2000, marked, !marked); //因为t1已经改过了,返回false
            System.out.println(Thread.currentThread().getName() + "\t" + "t2线程CASResult:" + b); //t2	t2线程CASResult:false
            System.out.println(Thread.currentThread().getName() + "\t" + markableReference.isMarked());//t2	true
            System.out.println(Thread.currentThread().getName() + "\t" + markableReference.getReference());//t2	1000

        }, "t2").start();
    }
}

4.对象的属性修改原子操作类

  • AtomicIntegerFieldUpdater:原子更新对象中int类型的属性。

  • AtomicLongFieldUpdater:原子更新对象中的Long类型的属性。

  • AtomicReferenceFieldUpdater:原子更行对象中引用类型的属性。

4.1使用原因

因为Synchronized锁的是整个对象,为了减少锁的粒度,我们使用对象属性原子操作类。

4.2使用要求

  1. 属性必须使用public volatile修饰。

  2. 需要使用newUpdater()创建更新器。

4.3AtomicReferenceFieldUpdater的使用

//只能有一个线程对其初始化
class MyVar {
    public volatile Boolean isInit = Boolean.FALSE;
    AtomicReferenceFieldUpdater<MyVar, Boolean> referenceFieldUpdater = AtomicReferenceFieldUpdater.newUpdater(MyVar.class, Boolean.class, "isInit");

    public void init(MyVar myVar) {
        //比较并交换进行修改
        if (referenceFieldUpdater.compareAndSet(myVar, Boolean.FALSE, Boolean.TRUE)) {
            System.out.println(Thread.currentThread().getName() + "\t" + "--------------start init ,need 2 secondes");
            try {
                TimeUnit.SECONDS.sleep(2);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println(Thread.currentThread().getName() + "\t" + "--------------over init");
        } else {
            System.out.println(Thread.currentThread().getName() + "\t" + "--------------已经有线程进行初始化工作了。。。。。");
        }
    }
}

public class AtomicReferenceFieldUpdaterDemo {

    public static void main(String[] args) {
        MyVar myVar = new MyVar();
        for (int i = 1; i <= 5; i++) {
            new Thread(() -> {
                myVar.init(myVar);
            }, String.valueOf(i)).start();
        }
    }
}
/**
 * 1	--------------start init ,need 2 secondes
 * 5	--------------已经有线程进行初始化工作了。。。。。
 * 2	--------------已经有线程进行初始化工作了。。。。。
 * 4	--------------已经有线程进行初始化工作了。。。。。
 * 3	--------------已经有线程进行初始化工作了。。。。。
 * 1	--------------over init
 */

 

5.原子操作增强类

  • LongAdder:维护了一个初始值从0开始的long类型的数据,只能用来计算加法,相较于AtomciLong,减少了自旋的次数。

  • LongAccumulator:相较于LongAdder,提供了自定义的函数操作,更加的灵活便捷。

5.1实现计数器的各种方法比较

/**
 * 需求:50个线程,每个线程100w此,算出总点赞数来
 */
class ClickNumber {
    int number = 0;

    public synchronized void clickBySynchronized() {
        number++;
    }

    AtomicLong atomicLong = new AtomicLong(0);

    public void clickByAtomicLong() {
        atomicLong.getAndIncrement();
    }

    LongAdder longAdder = new LongAdder();

    public void clickByLongAdder() {
        longAdder.increment();
    }

    LongAccumulator longAccumulator = new LongAccumulator((x, y) -> x + y, 0);

    public void clickByLongAccumulator() {
        longAccumulator.accumulate(1);
    }
}

public class AccumulatorCompareDemo {
    public static final int _1W = 10000;
    public static final int THREAD_NUMBER = 50;

    public static void main(String[] args) throws InterruptedException {
        ClickNumber clickNumber = new ClickNumber();
        long StartTime;
        long endTime;
        CountDownLatch countDownLatch1 = new CountDownLatch(THREAD_NUMBER);
        CountDownLatch countDownLatch2 = new CountDownLatch(THREAD_NUMBER);
        CountDownLatch countDownLatch3 = new CountDownLatch(THREAD_NUMBER);
        CountDownLatch countDownLatch4 = new CountDownLatch(THREAD_NUMBER);

        StartTime = System.currentTimeMillis();
        for (int i = 1; i <= 50; i++) {
            new Thread(() -> {
                try {
                    for (int j = 1; j <= 100 * _1W; j++) {
                        clickNumber.clickBySynchronized();
                    }
                } finally {
                    countDownLatch1.countDown();
                }
            }, String.valueOf(i)).start();
        }
        countDownLatch1.await();
        endTime = System.currentTimeMillis();
        System.out.println("------costTime: " + (endTime - StartTime) + " 毫秒" + "\t clickBySynchronized: " + clickNumber.number);

        StartTime = System.currentTimeMillis();
        for (int i = 1; i <= 50; i++) {
            new Thread(() -> {
                try {
                    for (int j = 1; j <= 100 * _1W; j++) {
                        clickNumber.clickByAtomicLong();
                    }
                } finally {
                    countDownLatch2.countDown();
                }
            }, String.valueOf(i)).start();
        }
        countDownLatch2.await();
        endTime = System.currentTimeMillis();
        System.out.println("------costTime: " + (endTime - StartTime) + " 毫秒" + "\t clickByAtomicLong: " + clickNumber.atomicLong.get());

        StartTime = System.currentTimeMillis();
        for (int i = 1; i <= 50; i++) {
            new Thread(() -> {
                try {
                    for (int j = 1; j <= 100 * _1W; j++) {
                        clickNumber.clickByLongAdder();
                    }
                } finally {
                    countDownLatch3.countDown();
                }
            }, String.valueOf(i)).start();
        }
        countDownLatch3.await();
        endTime = System.currentTimeMillis();
        System.out.println("------costTime: " + (endTime - StartTime) + " 毫秒" + "\t clickByLongAdder: " + clickNumber.longAdder.sum());

        StartTime = System.currentTimeMillis();
        for (int i = 1; i <= 50; i++) {
            new Thread(() -> {
                try {
                    for (int j = 1; j <= 100 * _1W; j++) {
                        clickNumber.clickByLongAccumulator();
                    }
                } finally {
                    countDownLatch4.countDown();
                }
            }, String.valueOf(i)).start();
        }
        countDownLatch4.await();
        endTime = System.currentTimeMillis();
        System.out.println("------costTime: " + (endTime - StartTime) + " 毫秒" + "\t clickByLongAccumulator: " + clickNumber.longAccumulator.get());

    }
}
/**
 * ------costTime: 1313 毫秒	 clickBySynchronized: 50000000
 * ------costTime: 825 毫秒	 clickByAtomicLong: 50000000
 * ------costTime: 92 毫秒	 clickByLongAdder: 50000000
 * ------costTime: 61 毫秒	 clickByLongAccumulator: 50000000
 */

5.2LongAdder为什么快?(源码分析)

        先从架构上进行分析,LongAdder继承了Striped64,Striped64继承了Number类,主要是Striped64类起到了关键性的作用。

具体为什么快?

        AtomicLong采用的是利用CAS原则对value值进行原子操作,这样多线程环境下的自旋会导致cpu资源大量的消耗,LongAdder的改进思路就是分散热点,在并发量小的情况下,仅对base进行原子操作,在并发量大的情况下,采用了Cell槽位,槽位里面用value值进行记录,这样不同的线程会命中到不同的槽位中,各个线程只对自己槽中的那个值进行CAS操作,这样的话高并发就被分散开了,要想获取到总的值,只需统计各个槽位中的value值和base的值,但是统计的数据不会高度一致(因为当sum统计时,还能对槽位数据进行操作),只能保证最终一致。

 

  1. 如果Cells数组为空,尝试用CAS更新base,成功则退出即可。

  2. 如果Cells数组为空,但是高并发下CAS更新base失败了,则调用longAccumulate()方法创建槽位,初始创建2个槽位,再次尝试CAS更新base,如果还是不行,则走“槽位法”。

  3. 如果Cells数组非空,但是当前的线程利用getProbe()算出的hash值映射到的槽位为空,则调用longAccumlate()方法对指定位置创建槽位并添加到Cell数组中,然后重新计算hash。

  4. 如果Cells数组非空,且当前线程映射的槽位不为空,CAS更新映射到的槽位的value值,成功则返回,否则,说明槽位竞争都非常大了,调用longAccumlate()方法对槽位进行扩容。

注意:槽位的个数只能为2^n个,最大不能超过CPU的核数。

5.3AtomicLong和LongAdder小总结

        AtomicLong可允许一些性能损耗,要求高精度时可使用,保证精度,多个线程对单个热点值value进行了原子操作,从而保证精度,但对Cpu资源的损耗较大。

        LongAdder当需要在高并发场景下有较好的性能表现,且对值的实时精确度要求不高时,可以使用,LongAdder中线程拥有自己的槽,各个线程只对自己槽中得那个value值进行CAS操作,从而保证性能,但牺牲了实时的精度代价,不过一定是能保证最终一致性的。

 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值