java

关注公众号 jb51net

关闭
首页 > 软件编程 > java > Java原子类

Java中的原子类详解

作者:似寒若暖

这篇文章主要介绍了Java中的原子类详解,Java原子类是一种多线程编程中常用的工具,用于实现线程安全的操作,它们提供了一种原子性操作的机制,确保多个线程同时访问共享变量时的数据一致性,需要的朋友可以参考下

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 + '\'' +
                '}';
    }
}

原子类

基本类型包装类-原子类

以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类的使用及问题 

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();
    }
}

原子数组类

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);
    }
}

字段更新器原子类

使用字段更新器,可以对对象的某个属性(域 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);
    }
}

原子累加器

虽然基本类型包装类自带原子累加操作方法,但是为了提高性能,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 重试失败,从而提高性能。

到此这篇关于Java中的原子类详解的文章就介绍到这了,更多相关Java原子类内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!

您可能感兴趣的文章:
阅读全文