07.Atomic与Unsafe

Atomic与Unsafe

Atomic

Atomic的类型

  • 原子整数
  • 原子引用
  • 原子数组
  • 字段更新器
  • 原子累加器

原子整数

原子整数类型下的类
  • AtomicInteger
  • AtomicLong
  • AtomicBoolean

因为上面这三个使用方式基本相同这里的例子只写AtomicInteger

AtomicInteger
  AtomicInteger atomicInteger = new AtomicInteger();
        //先获取再自增
        int a = atomicInteger.getAndIncrement(); //0
        System.out.println(a);
        //先自增再获取
        int b = atomicInteger.incrementAndGet(); //2
        System.out.println(b);

        //增加指定数值然后获取
        int c = atomicInteger.addAndGet(3); //5
        System.out.println(c);

        //乘除或其他复杂运算
        int i = atomicInteger.updateAndGet(x -> x * 10);
        System.out.println(i);

原子引用

原子引用类型下的类
  • AtomicReference
  • AtomicMarkableReference
  • AtomicStampedReference
AtomicReference
 AtomicReference<BigDecimal> atomicReference = new AtomicReference<BigDecimal>(new BigDecimal("1.5"));
        new Thread(() -> {
            while (true){
                BigDecimal bigDecimal = atomicReference.get();
                BigDecimal next = bigDecimal.add(BigDecimal.valueOf(1.5));
                if (atomicReference.compareAndSet(bigDecimal,next)){
                    break;
                }
            }
        }).start();
        new Thread(() -> {
            while (true){
                BigDecimal bigDecimal = atomicReference.get();
                BigDecimal next = bigDecimal.add(BigDecimal.valueOf(1.5));
                if (atomicReference.compareAndSet(bigDecimal,next)){
                    break;
                }
            }
        }).start();
        new Thread(() -> {
            while (true){
                BigDecimal bigDecimal = atomicReference.get();
                BigDecimal next = bigDecimal.add(BigDecimal.valueOf(1.5));
                if (atomicReference.compareAndSet(bigDecimal,next)){
                    break;
                }
            }
        }).start();
        Thread.sleep(1000);
        System.out.println(atomicReference.get());
ABA问题

如果另一个线程修改V值假设原来是A,先修改成B,再修改回成A。当前线程的CAS操作无法分辨当前V值是否发生过变化。如果有其他线程动过了共享变量,那么自己CAS就算失败,可以使用AtomicStampedReference和AtomicMarkableReference。 AtomicStampedReference可以给原子引用加上版本号,追踪原子引用整个的变化过程。如果只关心是否更改过而不关心引用变量更新了几次可以使用AtomicMarkableReference。

AtomicStampedReference
public class AtomicDemo04 {
    private static AtomicStampedReference<String> a = new AtomicStampedReference<>("A", 0);

    public static void main(String[] args) throws InterruptedException {
        String reference = a.getReference();
        int stamp = a.getStamp();
        update();
        Thread.sleep(100);
        boolean b = a.compareAndSet(reference, "B", stamp, stamp+1);
        System.out.println(b);
    }

    private static void update() throws InterruptedException {
        new Thread(() -> {
            a.compareAndSet(a.getReference(),"B", a.getStamp(), a.getStamp()+1);
        }, "t1").start();
        Thread.sleep(100);
        new Thread(() -> {
            a.compareAndSet(a.getReference(),"A",a.getStamp(), a.getStamp()+1);
        }, "t2").start();
    }
}
AtomicMarkableReference
public class AtomicDemo05 {
    private static AtomicMarkableReference<String> a = new AtomicMarkableReference<>("A",true);
    public static void main(String[] args) throws InterruptedException {
        String reference = a.getReference();
        update();
        Thread.sleep(100);
        boolean b = a.compareAndSet(reference, "B", true, false);
        System.out.println(b);
    }
    private static void update() throws InterruptedException {
        new Thread(() -> {
            a.compareAndSet(a.getReference(),"A", true,false);
        }, "t1").start();
    }
}

原子数组

原子引用类型下的类
  • AtomicIntegerArray
  • AtomicLongArray
  • AtomicReferenceArray
AtomicIntegerArray
public class AtomicDemo07 {
    public static void main(String[] args) throws InterruptedException {
        demo(
                () -> new int[10],
                (array) -> array.length,
                (array, index) -> array[index]++,
                array -> System.out.println(Arrays.toString(array))
        );
        demo(
                () -> new AtomicIntegerArray(10),
                (array) -> array.length(),
                (array, index) -> array.getAndIncrement(index),
                System.out::println
        );
    }

    private static <T> void demo(
            Supplier<T> arraySupplier,
            Function<T, Integer> lengthFun,
            BiConsumer<T, Integer> putConsumer,
            Consumer<T> printConsumer
    ) throws InterruptedException {
        List<Thread> ts = new ArrayList<>();
        T array = arraySupplier.get();
        int length = lengthFun.apply(array);
        for (int i = 0; i < length; i++) {
            ts.add(new Thread(() -> {
                for (int j = 0; j < 10000; j++) {
                    putConsumer.accept(array, j % length);
                }
            }));
        }
        ts.forEach(Thread::start);
        Thread.sleep(3000);
        printConsumer.accept(array);
    }
}

字段更新器

使用字段更新器,只能配合volatile修饰的字段使用而且必须是public的

原子引用类型下的类
  • AtomicReferenceFieldUpdater
  • AtomicIntegerFieldUpdater
  • AtomicLongFieldUpdater
AtomicReferenceFieldUpdater
public class AtomicDemo08 {
    public static void main(String[] args) {
        Student student = new Student();
        AtomicReferenceFieldUpdater<Student, String> studentStringAtomicReferenceFieldUpdater = AtomicReferenceFieldUpdater.newUpdater(Student.class, String.class, "name");
        String str = studentStringAtomicReferenceFieldUpdater.accumulateAndGet(student, "单国玉", (a,b)->{
            System.out.println(a);
            System.out.println(b);
            return a;
        });
        System.out.println(str);
    }
}

class Student {
    public volatile String name;

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    @Override
    public String toString() {
        return "Student{" +
                "name='" + name + '\'' +
                '}';
    }

    public Student() {
    }

    public Student(String name) {
        this.name = name;
    }
}

LongAdder

1.什么是LongAdder

LongAdder与AtomicLong是一样的都是Long自增但是LongAdder性能更好大概比AtomicLong高了好几倍。

LongAdder的思想

性能提升的原因,就是在有竞争时,设置多个累加单元,Thread-0累加Cell[0],而Thread-1 累加Cell[1]…最后将结果汇总。这样在累加时操作的不同的Cell变量,因此减少了CAS重试失败次数,从而提高性能(注意:累加单元不会多于CPU核心数)。

源码解析

1.Striped64

LongAdder的父类,是一个抽象类

  • 重要属性
	//当前计算机CPU数量,这个是控制cells数组的关键条件
    static final int NCPU = Runtime.getRuntime().availableProcessors();
    //cell数组
    transient volatile Cell[] cells;
    //没有发生竞争的时候,数据会累加到这个base身上
    transient volatile long base;
    //初始化cells或者扩容cells都需要获取锁,0表示无锁,1表示其他线程已经持有这把锁
    transient volatile int cellsBusy;
  • 静态内部类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);
        }
        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);
            }
        }
    }
  • 重要方法
//第一个参数x:自增量
//第二个参数fn:函数式接口,一般是null,扩展使用
//第三个参数wasUncontended:是否发生过竞争,只有cells初始化之后,并且当前线程竞争修改失败,才会是false,其他情况都是true
final void longAccumulate(long x, LongBinaryOperator fn,
                              boolean wasUncontended) {
		//都有哪几种情况会进入这个方法:
        //1.cells未初始化,也就是多线程写base发生竞争了——>会重试或初始化cells数组
        //2.说明当前线程对应的cell为空,需要创建
        //3.当前线程在CAS设置对应的cell的value的时候和其他线程发生了竞争导致CAS失败
		
		//表示线程的hash值
        int h;
        //获取当前线程的hash值赋值给h
        //如果hash值等于0说明当前线程还未分配hash值,会进入到代码块里进行分配hash值
        //如果hash值不等于0说明当前线程已经分配hash值了
        if ((h = getProbe()) == 0) {	  
            ThreadLocalRandom.current(); 
            h = getProbe();
            //因为默认情况下当前线程肯定是写入到了 cells[0]这个位置上,正常情况下肯定是会有竞争然后进入这个方法的,但是不会把这种情况当作真正的竞争情况。
            wasUncontended = true;
        }
        //表示扩容意向,false一定不会扩容,true可能会扩容
        boolean collide = false;                
        //自旋
        for (;;) {
        	//as表示cells引用
        	//a表示当前线程命中的cell
        	//n表示celss数组的长度
        	//v表示期望值 
            Cell[] as; Cell a; int n; long v;
            //表示cells已经初始化了,当前线程应该将数据写入到对应的cell中
            if ((as = cells) != null && (n = as.length) > 0) {
            	//根据线程hash码去获取当前线程对应的cell如果是空则去创建
                if ((a = as[(n - 1) & h]) == null) {
               		//cellsBusy == 0说明锁没有被占用
               		//cellsBusy!=0 说明有其他线程正在对cells数组进行扩容
                    if (cellsBusy == 0) {  
                    	//拿当前的值创建一个Cell     
                        Cell r = new Cell(x);  
                        //cellsBusy == 0——>表示当前锁未被占用可以去抢锁
                        //casCellsBusy()——>使用CAS去抢占锁
                        if (cellsBusy == 0 && casCellsBusy()) {
                        	//是否创建成功的一个标记
                            boolean created = false;
                            try {       
                            	//rs——>cells数组引用
                            	//m——>cells长度
                            	//j——>当前线程命中的下标
                                Cell[] rs; int m, j;
                                //条件1与条件2恒成立
                                //rs[j = (m - 1) & h] == null——>再次做一次检查因为线程切换的原因这里的cell可能不为null
                                if ((rs = cells) != null &&
                                    (m = rs.length) > 0 &&
                                    rs[j = (m - 1) & h] == null) {
                                    //向数组中添加元素和设置创建标记为true
                                    rs[j] = r;
                                    created = true;
                                }
                            } finally {
                            	//释放锁
                                cellsBusy = 0;
                            }
                            if (created)
                                break;
                            continue;           
                        }
                    }
                    //因为当前线程命中的cell为空还没有写入就扩容是不合理的
                    collide = false;
                }
                //当前线程对应的cell存在并且是因为第三种原因进入到这个方法才会走这个代码块
                else if (!wasUncontended)       
                    wasUncontended = true;   
                //当前线程重置过hash值,然后新命中的cell不为空则会来到这里
                //当前线程会对新命中的cell值进行CAS设置,如果成功则退出
                //如果CAS失败表示重置hash之后命中的cell也有竞争
                else if (a.cas(v = a.value, ((fn == null) ? v + x :
                                             fn.applyAsLong(v, x))))
                    break;
                //如果cells数组的长度大于等于CPU核心数则不在扩容(设置扩容意向为false)
                //如果cells!=as说明其他线程已经扩容过了也设置扩容意向为不在扩容
                else if (n >= NCPU || cells != as)
                    collide = false;      
                //!collide为ture表示设置扩容意向为true(只是意向而不是真的要去扩容)
                else if (!collide)
                    collide = true;
                //cells数组扩容逻辑逻辑
                //cellsBusy == 0 ——>表示当前是无锁状态,当前线程可以去竞争这把锁
                //casCellsBusy()——>使用CAS尝试去获取这把锁
                else if (cellsBusy == 0 && casCellsBusy()) {
                    try {
                    	//再次检查
                        if (cells == as) {  
                        	//创建一个新的Cell数组容量是原先的2倍    
                            Cell[] rs = new Cell[n << 1];
                            //遍历老的数组把值放到新的数组当中
                            for (int i = 0; i < n; ++i)
                                rs[i] = as[i];
                            //新的引用赋值给全局变量
                            cells = rs;
                        }
                    } finally {
                    	//释放锁
                        cellsBusy = 0;
                    }
                    collide = false;
                    continue;                   
                }
                //重置当前线程hash值,这会导致原先当前线程可能写入[0]的cell中重置后写道[1]的cell中
                h = advanceProbe(h);
            }
            //celss还未初始化(as等于null)
            //初始化cells数组
            //条件1——>拿到锁
            //条件2——>由于是并发的所以要在判断一下celss与as是否相等
            //条件3——>尝试获取锁(把cellsBusy用CAS从0改为1)
            else if (cellsBusy == 0 && cells == as && casCellsBusy()) {
                boolean init = false;
                try {          
                	//再次检查celss数组防止celss已经被其他线程初始化了                
                    if (cells == as) {
                        Cell[] rs = new Cell[2];
                        rs[h & 1] = new Cell(x);
                        cells = rs;
                        init = true;
                    }
                } finally {
                    cellsBusy = 0;
                }
                if (init)
                    break;
            }
            //1.获取锁失败表示其他线程正在初始化celss,celss已经被其他线程初始化,这两种情况都会导致进入到下面这个代码块中
            //2.这个else if的逻辑就是把值累加到base上
            else if (casBase(v = base, ((fn == null) ? v + x :
                                        fn.applyAsLong(v, x))))
                break;                          
        }
    }
2.LongAdder
  • 重要方法add()
public void add(long x) {
		//as 表示cells的引用
		//b	 表示获取的base值
		//v	 表示期望值
		//m  表示cells数组的长度
		//a  表示当前线程命中的cell单元格
        Cell[] as; long b, v; int m; Cell a;
        //条件1:true——>表示cells已经初始化过了,当前线程应该将数据写入到对应的cell中
        //		false——>表示cells未初始化,当前所有线程应该将数据写到base中

		//条件2:true——>表示当前线程CAS成功
		//		false——>表示发生竞争了,可能需要重试或扩容
        if ((as = cells) != null || !casBase(b = base, b + x)) {
        	//什么时候会进入这个代码块(满足一个条件就会进入这个代码块):
        	//1.cells已经初始化过了
        	//2.CAS发生竞争设置BASE失败

			// true——>没有发生竞争
			// false——>发生竞争
            boolean uncontended = true;
            // (as == null || (m = as.length - 1) < 0)——>看cells是否初始化
            //结果为true——>说明cells未初始化,这个线程是因为CAS失败进入这个代码块的。
            //结果为false——>说明cells已经初始化了,当前线程应该是找自己的cell写值。
            
            //(a = as[getProbe() & m])——>看当前线程对应下标的cell是否为空
            //getProbe()——>获取当前线程的hash值,m表示cells长度-1
            //as[getProbe() & m]——>获得的值是小于等于celss长度-1
            //a = as[getProbe() & m]) == null——根据一个下标获取cell
            //(a = as[getProbe() & m])——>这个为true表示当前线程对应下标的cell为空,需要创建 longAccumulate支持
			//(a = as[getProbe() & m])——>这个为false表示当前线程对应下标的cell不为空,不需要创建 下一步就是增加cell的value

			//!(uncontended = a.cas(v = a.value, v + x))——>通过CAS修改当前线程对应的cell节点的value
			//true表示CAS失败,意味着当前线程对应的cell有其他线程也在改发生了竞争
			//false表示CAS设置成功,
            if (as == null || (m = as.length - 1) < 0 ||
                (a = as[getProbe() & m]) == null ||
                !(uncontended = a.cas(v = a.value, v + x)))
                //都有哪几种情况会调用这个方法:
                //1.cells未初始化,也就是多线程写base发生竞争了——>会重试或初始化cells数组
                //2.说明当前线程对应的cell为空,需要创建
                //3.当前线程在CAS设置对应的cell的value的时候和其他线程发生了竞争导致CAS失败
                longAccumulate(x, null, uncontended);
        }
    }

Unsafe

Unsafe介绍

Unsafe对象提供了非常底层的,操作内存,线程的方法,Unsafe对象不能直接调用,只能通过反射获得。

Unsafe对象的获取与CAS相关方法代码

/**
 * @author 单国玉
 * @date 2021/5/31
 * @description Unsafe对象的获取以及CAS的相关方法
 */
public class UnsafeDemo01 {
    public static void main(String[] args) throws NoSuchFieldException, IllegalAccessException {
        Field theUnsafe = Unsafe.class.getDeclaredField("theUnsafe");
        theUnsafe.setAccessible(true);
        Unsafe unsafe = (Unsafe) theUnsafe.get(null);

        Teacher t = new Teacher();
        t.id=1;
        t.name="单国玉";

        //1.获取属性偏移量
        long idOffset = unsafe.objectFieldOffset(Teacher.class.getDeclaredField("id"));
        long nameOffset = unsafe.objectFieldOffset(Teacher.class.getDeclaredField("name"));

        //2.执行CAS操作
        boolean b = unsafe.compareAndSwapInt(t, idOffset, 1, 2);
        System.out.println(b);

        boolean c = unsafe.compareAndSwapObject(t, nameOffset, "单国玉", "shanguoyu");
        System.out.println(c);

        System.out.println(t.toString());
    }
}

class Teacher {
    volatile int id;
    volatile String name;

    @Override
    public String toString() {
        return "Teacher{" +
                "id=" + id +
                ", name='" + name + '\'' +
                '}';
    }
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值