原子类详解

Unsafe

概述

  • 在将原子类之前,先介绍下Unsafe类,因为原子类的操作都是基于该类做的
  • Unsafe对象提供了非常底层操作内存、线程的方法
  • 该类只提供了一个私有的无参构造,且Unsafe的定义是一个的私有成员变量,源码如下
public final class Unsafe {
	 //私有成员静态变量
    private static final Unsafe theUnsafe;
    
    // 构造器
    private Unsafe() {
    }
  • 由上一点可知Unsafe对象只能通过反射获取,不能直接创建,获取方式如下
public class UnsafeUtil {
    static Unsafe unsafe;
    static {
        try {
            Field theUnsafe = Unsafe.class.getDeclaredField("theUnsafe");
            theUnsafe.setAccessible(true);
            unsafe = (Unsafe) theUnsafe.get(null);
        } catch (NoSuchFieldException | IllegalAccessException e) {
            throw new RuntimeException("获取 Unsafe 对象异常");
        }
    }
    static Unsafe getUnsafe() {
        return unsafe;
    }
}

Unsafe的cas

  • Unsafe的cas操作是通过对象属性的偏移量、旧值、新值
  • 要操作的属性还需要使用volatile修饰,否则会报非法参数异常
  • 示例如下
public class UnsafeCas {
    public static void main(String[] args) throws NoSuchFieldException {
        Dog dog = new Dog();
        Unsafe unsafe = UnsafeUtil.getUnsafe();
        // 通过反射获取类中属性的域对象 --> 供Unsafe对象获取域对象偏移量使用
        Field id = Dog.class.getDeclaredField("id");
        Field age = Dog.class.getDeclaredField("age");
        Field name = Dog.class.getDeclaredField("name");
        // 根据域对象获取域对象的偏移量 --> 供Unsafe cas操作时使用
        long idOffset = unsafe.objectFieldOffset(id);
        long ageOffset = unsafe.objectFieldOffset(age);
        long nameOffset = unsafe.objectFieldOffset(name);
        // Unsafe cas 操作, 参数为要操作对象,属性偏移量,旧值,新值
        // 如果要保证并发,在加上while(true)循环判断该cas操作是否成功来控制循环退出条件即可
        unsafe.compareAndSwapInt(dog,idOffset,0,1);
        unsafe.compareAndSwapInt(dog,ageOffset,0,5);
        unsafe.compareAndSwapObject(dog,nameOffset,null,"狗狗");
        System.out.println(dog);
    }
}

class Dog{
    volatile long id;
    volatile int age;
    volatile String name;

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

原子类

基本类型包装类-原子类

  • AtomicBoolean(原子Boolean类)
  • AtomicInteger(原子整形类)
  • AtomicLong(原子长整型类)

以AtomicInteger为例介绍下该组相关API

public class AtomicIntegerApi {

    public static void main(String[] args) {
        AtomicInteger i = new AtomicInteger(0);

        // 类似于 i++,获取并自增(i = 0, 结果 i = 1, 返回 0)
        System.out.println(i.getAndIncrement());
        System.out.println(i.get());

        // 类似于 ++i,自增并获取(i = 1, 结果 i = 2, 返回 2)
        System.out.println(i.incrementAndGet());
        System.out.println(i.get());

        // 类似于 --i,自减并获取(i = 2, 结果 i = 1, 返回 1)
        System.out.println(i.decrementAndGet());
        System.out.println(i.get());

        // 类似于 i--,获取并自减(i = 1, 结果 i = 0, 返回 1)
        System.out.println(i.getAndDecrement());
        System.out.println(i.get());

        /*
         * 上述加减操作单元都是1,如果想自定义操作单元,可以用下面方法
         * */
        // 获取并加值(i = 0, 结果 i = 5, 返回 0)
        System.out.println(i.getAndAdd(5));
        System.out.println(i.get());

        // 加值并获取(i = 5, 结果 i = 0, 返回 0)
        System.out.println(i.addAndGet(-5));
        System.out.println(i.get());

        /*
        * 上述均为加减操作,若想进行更新操作可以使用如下方法,
        * 入参为一元整型操作函数式接口: IntUnaryOperator updateFunction
        * */
        // 获取并更新(i = 0, p 为 i 的当前值, 结果 i = -2, 返回 0)
        // 其中函数中的操作能保证原子,但函数需要无副作用
        System.out.println(i.getAndUpdate(p -> p - 2));
        System.out.println(i.get());

        // 更新并获取(i = -2, p 为 i 的当前值, 结果 i = 0, 返回 0)
        // 其中函数中的操作能保证原子,但函数需要无副作用
        System.out.println(i.updateAndGet(p -> p + 2));
        System.out.println(i.get());


        /*
         * 若想进行两个参数测操作,可以使用如下方法
         * 入参为:参数一;要操作的第二个整除 ,参数二:一元整型操作函数式接口: IntUnaryOperator updateFunction
         * */
        // 获取并计算(i = 0, p 为 i 的当前值, x 为参数1, 结果 i = 10, 返回 0)
        // 其中函数中的操作能保证原子,但函数需要无副作用
        // getAndUpdate 如果在 lambda 中引用了外部的局部变量,要保证该局部变量是 final 的
        // getAndAccumulate 可以通过 参数1 来引用外部的局部变量,但因为其不在 lambda 中因此不必是 final
        System.out.println(i.getAndAccumulate(10, (p, x) -> p + x));
        System.out.println(i.get());

        // 计算并获取(i = 10, p 为 i 的当前值, x 为参数1, 结果 i = 0, 返回 0)
        // 其中函数中的操作能保证原子,但函数需要无副作用
        System.out.println(i.accumulateAndGet(-10, (p, x) -> p + x));
        System.out.println(i.get());

    }
}

引用类-原子类

  • AtomicReference(基础原子引用类)
  • AtomicMarkableReference(可以判断共享变量是否修改过的原子引用类)
  • AtomicStampedReference(带戳的原子引用类)
AtomicReference类的使用及问题
public class AtomicReferenceUser {
    static AtomicReference<String> ref = new AtomicReference<>("A");
    public static void main(String[] args){
        System.out.println("main start...");
        // 获取值 A
        String prev = ref.get();
        // 尝试改为 C
        System.out.println("change A->C {}" + ref.compareAndSet(prev, "C"));
    }
}
  • 使用基本原子引用类,会引发ABA问题:也就是如下场景,如果一个线程t1在执行开始时获取到共享变量为A,t1线程执行,这是t2线程将共享变量由A->B并执行结束,又有一个线程t3将B->A并执行结束,这是线程t1要将共享变量由A变为C,这时是会成功的。代码如下
public class ABAThreadSafeQuestion {
    static AtomicReference<String> ref = new AtomicReference<>("A");
    public static void main(String[] args) throws InterruptedException {
        System.out.println("main start...");
        // 获取值 A
        // 这个共享变量被它线程修改过?
        String prev = ref.get();
        other();
        TimeUnit.SECONDS.sleep(1);
        // 尝试改为 C
        System.out.println("change A->C {}" + ref.compareAndSet(prev, "C"));
    }

    private static void other() {
        new Thread(() -> {
            System.out.println("change A->B {}" + ref.compareAndSet(ref.get(), "B"));
        }, "t1").start();
        sleep(0.5);
        new Thread(() -> {
            System.out.println("change B->A {}" + ref.compareAndSet(ref.get(), "A"));
        }, "t2").start();
    }
}
AtomicMarkableReference类的使用及问题
  • 使用AtomicReference原子类操作是会引发ABA问题,如果我只关心共享变量是否被修改过,则可以使用AtomicMarkableReference原子类来解决ABA问题,其他线程修改时,原子变量的第二个参数
public class AtomicMarkableReferenceSloveABA {
    static AtomicMarkableReference<String> ref = new AtomicMarkableReference<>("A",Boolean.TRUE);
    public static void main(String[] args) throws InterruptedException {
        System.out.println("main start...");
        // 获取值 A
        String prev = ref.getReference();
        other();
        TimeUnit.SECONDS.sleep(1);
        // 尝试改为 C
        System.out.println("change A->C {}" + ref.compareAndSet(prev, "C",Boolean.TRUE,Boolean.FALSE));
    }

    private static void other() {
        new Thread(() -> {
            System.out.println("change A->B {}" + ref.compareAndSet(ref.getReference(),"B",Boolean.TRUE,Boolean.FALSE));
        }, "t1").start();
        sleep(0.5);
        new Thread(() -> {
            System.out.println("change B->A {}" + ref.compareAndSet(ref.getReference(), "A",Boolean.TRUE,Boolean.FALSE));
        }, "t2").start();
    }
}
AtomicStampedReference原子类的使用及问题

AtomicMarkableReference原子类虽然可以解决ABA问题,但是如果需要知道具体修改过几次,那么
AtomicMarkableReference原子类就不能满足需求了,若要解决这个问题,则就要使用AtomicStampedReference原子类

public class ThreadSafeImplByAtomicStampedReference{

    static AtomicStampedReference<String> ref = new AtomicStampedReference<>("A", 0);

    public static void main(String[] args) throws InterruptedException {
        System.out.println("main start...");
        // 获取值A
        String prev = ref.getReference();
        // 获取版本号
        int stamp = ref.getStamp();
        System.out.println("版本 {}" + stamp);
        // 如果中间有其它线程干扰,发生了 ABA 现象
        other();
        sleep(1);
        // 尝试改为 C
        System.out.println("change A->C {}" + ref.compareAndSet(prev, "C", stamp, stamp + 1));
    }
    private static void other() {
        new Thread(() -> {
            System.out.println("change A->B {}" + ref.compareAndSet(ref.getReference(), "B",
                    ref.getStamp(), ref.getStamp() + 1));
            System.out.println("更新版本为 {}" + ref.getStamp());
        }, "t1").start();
        sleep(0.5);
        new Thread(() -> {
            System.out.println("change B->A {}" + ref.compareAndSet(ref.getReference(), "A",
                    ref.getStamp(), ref.getStamp() + 1));
            System.out.println("更新版本为 {}" + ref.getStamp());
        }, "t2").start();
    }
}

原子数组类

  • AtomicIntegerArray(整型原子数组类)
  • AtomicLongArray(长整型原子数组类)
  • AtomicReferenceArray(引用对象原子数组类)
public class AtomicArray {

    public static void main(String[] args) {
        // 不安全数组
        test(
                ()->new int[10],
                (array)->array.length,
                (array, index) -> array[index]++,
                array-> System.out.println(Arrays.toString(array))
        );
        // 原子数组
        test(
                ()-> new AtomicIntegerArray(10),
                (array) -> array.length(),
                (array, index) -> array.getAndIncrement(index),
                array -> System.out.println(array)
        );
    }

    /**
     参数1,提供数组、可以是线程不安全数组或线程安全数组
     参数2,获取数组长度的方法
     参数3,自增方法,回传 array, index
     参数4,打印数组的方法
     */
    private static <T> void test(
            Supplier<T> arraySupplier,
            Function<T, Integer> lengthFun,
            BiConsumer<T, Integer> putConsumer,
            Consumer<T> printConsumer ) {
        List<Thread> ts = new ArrayList<>();
        T array = arraySupplier.get();
        int length = lengthFun.apply(array);
        for (int i = 0; i < length; i++) {
            // 每个线程对数组作 10000 次操作
            ts.add(new Thread(() -> {
                for (int j = 0; j < 10000; j++) {
                    putConsumer.accept(array, j%length);
                }
            }));
        }
        // 启动所有线程
        ts.forEach(t -> t.start());
        // 等所有线程结束
        ts.forEach(t -> {
            try {
                t.join();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        });
        printConsumer.accept(array);
    }
}

字段更新器原子类

  • AtomicReferenceFieldUpdater
  • AtomicIntegerFieldUpdater
  • AtomicLongFieldUpdater

使用字段更新器,可以对对象的某个属性(域 Field)进行原子操作,只能配合 volatile 修饰的字段使用,否则会出现异常:java.lang.IllegalArgumentException: Must be volatile type

public class AtomicFieldUpdaterTest {

    private volatile int field;

    public static void main(String[] args) {
        AtomicIntegerFieldUpdater fieldUpdater =AtomicIntegerFieldUpdater.newUpdater(AtomicFieldUpdaterTest.class, "field");
        AtomicFieldUpdaterTest test5 = new AtomicFieldUpdaterTest();
        fieldUpdater.compareAndSet(test5, 0, 10);
        // 修改成功 field = 10
        System.out.println(test5.field);
        // 修改失败 field = 10
        fieldUpdater.compareAndSet(test5, 5, 30);
        System.out.println(test5.field);
    }
}

原子累加器

  • DoubleAccumulator
  • DoubleAdder
  • LongAccumulator
  • LongAdder

虽然基本类型包装类自带原子累加操作方法,但是为了提高性能,JDK8中又提供了原子累加器,下面是效率对比测试类

public class AtomicLongAdderTest {

    public static void main(String[] args) {
        for (int i = 0; i < 5; i++) {
            demo(() -> new LongAdder(), adder -> adder.increment());
        }
        for (int i = 0; i < 5; i++) {
            demo(() -> new AtomicLong(), adder -> adder.getAndIncrement());
        }
    }

    private static <T> void demo(Supplier<T> adderSupplier, Consumer<T> action) {
        T adder = adderSupplier.get();
        long start = System.nanoTime();
        List<Thread> ts = new ArrayList<>();
        // 4 个线程,每人累加 50 万
        for (int i = 0; i < 40; i++) {
            ts.add(new Thread(() -> {
                for (int j = 0; j < 500000; j++) {
                    action.accept(adder);
                }
            }));
        }
        ts.forEach(t -> t.start());
        ts.forEach(t -> {
            try {
                t.join();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }

        });
        long end = System.nanoTime();
        System.out.println(adder + " cost:" + (end - start)/1000000);
    }
}

性能提升的原因很简单,就是在有竞争时,设置多个累加单元,Therad-0 累加 Cell[0],而 Thread-1 累加Cell[1]… 最后将结果汇总。这样它们在累加时操作的不同的 Cell 变量,因此减少了 CAS 重试失败,从而提高性能。

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

似寒若暖

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值