自学内容网 自学内容网

JUC:原子类型的使用(原子整数、原子引用、原子数组、字段更新器、累加器)

原子类型

AtomicInteger 原子整数

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

        System.out.println(i.getAndIncrement()); // i++ sout: 0
        System.out.println(i.incrementAndGet()); // ++i sout: 2
        System.out.println(i.decrementAndGet()); // --i sout: 1
        System.out.println(i.getAndDecrement()); // i-- sout: 1
        // i = 0
        System.out.println(i.getAndAdd(5)); // sout:0 i += 5
        System.out.println(i.addAndGet(-5)); // i -= 5 sout:0
        // i = 0
        System.out.println(i.getAndUpdate(p -> p - 2)); // sout:0 p -= 2
        System.out.println(i.updateAndGet(p -> p + 2)); // p += 2 sout: 0
    }
}

在这里插入图片描述

AtomicReferenc 原子引用

class DecimalAccountSafeCas{
    AtomicReference<BigDecimal> ref;

    public DecimalAccountSafeCas(BigDecimal balance) {
        ref = new AtomicReference<>(balance);
    }

    public BigDecimal getBalance() {
        return ref.get();
    }

    public void withdraw(BigDecimal amount) {
        while (true) {
            BigDecimal prev = ref.get();
            BigDecimal next = prev.subtract(amount); // 减小
            if (ref.compareAndSet(prev, next)) { // cas
                break;
            }
        }
    }
}

AtomicStampedReference 带版本号的原子引用

解决aba问题,aba问题之前介绍过了。
如果版本号与之前获取的相同,那么就进行交换,同时更新版本号。
可以知道更改次数。

class DecimalAccountSafeCas{
    AtomicStampedReference<BigDecimal> ref;

    public DecimalAccountSafeCas(BigDecimal balance) {
        ref = new AtomicStampedReference<>(balance, 0); // 初始版本号0
    }

    public BigDecimal getBalance() {
        return ref.getReference();
    }

    public void withdraw(BigDecimal amount) {
        while (true) {
            BigDecimal prev = ref.getReference();
            int stamp = ref.getStamp();// 获取版本号
            // 如果这之中有其他线程又修改了ref,那么版本号改变与之前不同。就会cas失败    
            BigDecimal next = prev.subtract(amount); // 减小
            if (ref.compareAndSet(prev, next, stamp, stamp + 1)) { // cas。版本号相同才交换,并且更新版本号
                break;
            }
        }
    }
}

AtomicMarkableReference 仅记录是否修改的原子引用

我们一般不关心中间修改了多少次,我么只在乎是否修改过,那么就可以用这个类。

所以一个boolean值就可以记录。

class DecimalAccountSafeCas{
    AtomicMarkableReference<BigDecimal> ref;

    public DecimalAccountSafeCas(BigDecimal balance) {
        ref = new AtomicMarkableReference<>(balance, true);
    }

    public BigDecimal getBalance() {
        return ref.getReference();
    }

    public void withdraw(BigDecimal amount) {
        while (true) {
            BigDecimal prev = ref.getReference();
            BigDecimal next = prev.subtract(amount); // 减小
            // 如果这时有线程修改,把mark变为了false,那么就会cas就会失败
            // 因为只有true,和false,我们就直接就对比是否是true就行了,不用在获取
            if (ref.compareAndSet(prev, next, true, false)) { // cas。版本号相同才交换,并且更新版本号
                break;
            }
        }
    }
}

AtomicXXXArray 原子数组

我么一般都不是修改引用指向,而是引用里的内容,就比如数组。

下面是线程安全的数组。

  • AtomicIntegerArray
  • AtomicLongArray
  • AtomicReferenceArray

这里我么先用普通数组,来看看是否有线程安全问题:

package com.leran;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.function.BiConsumer;
import java.util.function.Consumer;
import java.util.function.Function;
import java.util.function.Supplier;

public class test7 {

    public static void main(String[] args) {

        demo(
                () -> new int[10],
                (array) ->  array.length,
                (array, idx) -> array[idx]++,
                array -> System.out.println(Arrays.toString(array))
        );
    }

    private static <T> void demo(
        Supplier<T> arraySupplier, // 生产者,无中生有
        Function<T, Integer> lengthFun, // 一个参数一个结果 BiFunction 多个参数,多个结果
        BiConsumer<T, Integer> putConsumer, // 消费者多个参数,无结果
        Consumer<T> printConsumer ) { // 一个参数,无结果

        List<Thread> ts = new ArrayList<>();
        T array = arraySupplier.get(); // 获取传入的数组
        int length = lengthFun.apply(array); // 获取长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);
    }
}

在这里插入图片描述

显然是不安全的,下面介绍安全的AtomicIntegerArray。

demo方法不变,改变传入的数组。

    demo(
            () -> new AtomicIntegerArray(10),
            (array) ->  array.length(),
            (array, idx) -> array.getAndIncrement(idx),
            array -> System.out.println(array)
    );

在这里插入图片描述

其他用法相似。

AtomicXXXFieldUpdater 字段更新器

用于更新某一类中的属性。

  • AtomicReferenceFieldUpdater // 域字段
  • AtomicIntegerFieldUpdater
  • AtomicLongFieldUpdater

必须配合volatile使用。

如:AtomicIntegerFieldUpdater

public class test8 {
    public static void main(String[] args) {
        AtomicIntegerFieldUpdater fieldUpdater = AtomicIntegerFieldUpdater.newUpdater(Student.class, "age");
        Student student = new Student();
        fieldUpdater.compareAndSet(student, 0, 5);
        System.out.println(student.age); // 5
        fieldUpdater.compareAndSet(student,5, 10);
        System.out.println(student.age); // 10
        fieldUpdater.compareAndSet(student, 5, 20); // 修改失败
        System.out.println(student.age); // 10
    }
}

class Student{
    volatile int age;

    @Override
    public String toString() {
        return "student{" +
                "age=" + age +
                '}';
    }
}

在这里插入图片描述

其他用法相似。

LongAdder累加器

java中专门用于累加的,所以性能肯定比我么AtomicLong好。
下面是对比。

public class test9 {
    public static void main(String[] args) {
        for (int i = 0; i < 5; i++) {
            demo(() -> new AtomicLong(), adder -> adder.getAndIncrement());
        }
        for (int i = 0; i < 5; i++) {
            demo(() -> new LongAdder(), adder -> adder.increment());
        }
    }
    private static <T> void demo(Supplier<T> adderSupplier, Consumer<T> action) {
        T adder = adderSupplier.get();

        long start = System.nanoTime();

        List<Thread> ts = new ArrayList<>();
        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)/1000_000 + "ns");
    }
}

前五次是AtomicLong累加
后五次是LongAdder累加

20000000 cost:414ns
20000000 cost:390ns
20000000 cost:366ns
20000000 cost:377ns
20000000 cost:414ns
20000000 cost:33ns
20000000 cost:26ns
20000000 cost:26ns
20000000 cost:27ns
20000000 cost:37ns

Process finished with exit code 0

显然是快了10倍左右。

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


原文地址:https://blog.csdn.net/Cosmoshhhyyy/article/details/137348890

免责声明:本站文章内容转载自网络资源,如本站内容侵犯了原著者的合法权益,可联系本站删除。更多内容请关注自学内容网(zxcms.com)!