Java.JUC: 原子类

96 阅读6分钟

java.utils.concurrent.atomic

此包下存放了大量java的原子类;所谓原子类,是java为了实现在多线程环境下保证对象实现原子性操作而设计的,可以避免在多线程环境中的同步问题(如使用 synchronized 关键字)。这意味着它们可以提供原子性的读-修改-写操作,不需要显式的同步机制。

原子类保证了原子性,并通过volatile保证了一定程度上的有序性和可见性

基本类型的原子操作类 AtomicInteger

AtomicInteger提供了原子性的基本整型操作,如增加、减少、设置值等,无需显式加锁。

多线程下的非原子类

public class AtomicIntegerDemo {
    private static Integer count = 0;

    public static void increment() {
        count++;
    }

    public static void main(String[] args) throws InterruptedException {
        Thread thread = new Thread(() -> {
            for (int i = 0; i < 1000; i++) {
                increment();
            }
        });
        Thread thread2 = new Thread(() -> {
            for (int i = 0; i < 1000; i++) {
                increment();
            }
        });
        thread.start();
        thread2.start();
        thread.join();
        thread2.join();
        System.out.println("Count: " + count);
    }
}

Count: 1384

public class AtomicIntegerDemo {
    private static AtomicInteger count = new AtomicInteger(0);

    public static void increment() {
        count.incrementAndGet();
    }

    public static void main(String[] args) throws InterruptedException {
        Thread thread = new Thread(() -> {
            for (int i = 0; i < 1000; i++) {
                increment();
            }
        });
        Thread thread2 = new Thread(() -> {
            for (int i = 0; i < 1000; i++) {
                increment();
            }
        });
        thread.start();
        thread2.start();
        thread.join();
        thread2.join();
        System.out.println("Count: " + count);
    }
}

Count: 2000

可以看出原子类的作用

原子性实现

CAS (Compare and Swap)译为,比较并操作;是一类基本的处理多线程的算法。CAS 操作包含三个参数:当前值 V、期望值 A 和新值 B。当且仅当 V 的位置的值等于 A 时,才会将 A 替换为 B。否则,操作失败,并可能再次尝试。if (V == A) V = B else someting

除了CAS,java还在虚拟机层面实现了Unsafe 类:sun.misc.Unsafe 类来进行底层的 CAS 操作。Unsafe 类提供了直接访问底层内存的方法。(唤醒了Rust记忆,计算机本身就是非安全的,哪怕是以安全著称的Rust也需要unsafe操作)

这个是最顶层的操作

public final int incrementAndGet() {
    return U.getAndAddInt(this, VALUE, 1) + 1;
}

上面的操作调用了Unsafe的getAndAddInt

// VALUE = U.objectFieldOffset(AtomicInteger.class, "value");
@IntrinsicCandidate
public final int getAndAddInt(Object o, long offset, int delta) {
    int v;
    do {
        v = getIntVolatile(o, offset);  // 获取期望值
    } while (!weakCompareAndSetInt(o, offset, v, v + delta));
    return v;
}

在UnSafe类中,程序不断去获得当前的值v,其中offset提供了内存偏移位置, 然后去执行CAS操作;失败则继续循环

上面分别涉及到两个方法

/** Volatile version of {@link #getInt(Object, long)}  */
@IntrinsicCandidate
public native int getIntVolatile(Object o, long offset);

这个是getInt的保证可见性的版本

@IntrinsicCandidate
public final boolean weakCompareAndSetInt(Object o, long offset, int expected, int x) {
    // o+offset译为当前值;expect设为期望值,x是新值
    return compareAndSetInt(o, offset, expected, x);
}

/**
 * Atomically updates Java variable to x if it is currently holding expected
 *
 * This operation has memory semantics of a {@code volatile} read and write.  
 * Corresponds to C11 atomic_compare_exchange_strong.
 *
 * @return  true if successful
 */
@IntrinsicCandidate
// o+offset译为当前值;expect设为期望值,x是新值
public final native boolean compareAndSetInt(Object o, long offset, int expected, int x);

之前在循环中获得的 v 没有被改变,则o+offset == expect(x), CAS就会成功

CAS问题,无法处理A->B->A问题; 因为CAS只要求比较时符合要求就好,至于中间是否发生了变化,CAS不关心

其他操作方法基本和上面所叙述的运行相同,只不过是方法调用层次不同;当然除了传入数值,java还支持传入自定义函数,详情可见public final int updateAndGet(IntUnaryOperator updateFunction)

public final int updateAndGet(IntUnaryOperator updateFunction) {
    int prev = get(), next = 0;
    for (boolean haveNext = false;;) {
        if (!haveNext)
            next = updateFunction.applyAsInt(prev);
        if (weakCompareAndSetVolatile(prev, next))
            return next;
        haveNext = (prev == (prev = get()));
    }
}
public final boolean weakCompareAndSetVolatile(int expectedValue, int newValue) {
    return U.weakCompareAndSetInt(this, VALUE, expectedValue, newValue);
}

同于普通操作

  1. 先获取当前值作为期望值
  2. 然后获得目标值
  3. JVM中再取当前值与期望值比较

有序性与可见性

有序性与可见性由volatilegetIntVolatile提供

private volatile int value;

常见问题与易错点

  • 误解原子性:认为任何基于AtomicInteger的操作都是绝对线程安全的。实际上,原子性仅保证了单个操作的不可分割性,复杂逻辑依然可能需要额外同步。
  • 过度依赖原子类:在某些情况下,过度使用原子类可能不如使用synchronizedLock来得直观和高效,特别是在涉及多个变量的复合操作时。

引用类型的原子操作类AtomicReference

Java只提供了基础数据类型的原则类,如果我们希望实现自己的类的原子性则要使用AtomicReference; 原子性,有序性与可见性的处理方式与上面相同, 只不过从底层操作从unsafe改为了varhandle

使用AtomicReference包装Integer

public class AtomicIntegerDemo {
    private static AtomicReference<Integer> count = new AtomicReference<>(0);

    public static void increment() {
        count.updateAndGet(v -> v + 1);
    }

    public static void main(String[] args) throws InterruptedException {
        Thread thread = new Thread(() -> {
            for (int i = 0; i < 1000; i++) {
                increment();
            }
        });
        Thread thread2 = new Thread(() -> {
            for (int i = 0; i < 1000; i++) {
                increment();
            }
        });
        thread.start();
        thread2.start();
        thread.join();
        thread2.join();
        System.out.println("Count: " + count);
    }
}

常见问题与易错点

  • 误解:认为任何基于AtomicReference的操作都是绝对线程安全的。AtomicReference只保证对对象的值得改变是安全的,至于对象内部,这不是它的工作范围

例如

class myInt {
    private int value;

    public myInt(int value) {
        this.value = value;
    }

    public void increment() {
        value++;
    }

    public int getValue() {
        return value;
    }
}

public class AtomicReferenceDemo {
    private static AtomicReference<myInt> count = new AtomicReference<>(new myInt(0));

    public static void increment() {
        count.updateAndGet(myInt -> {
            myInt.increment();
            return myInt;
        });
    }

    public static void main(String[] args) throws InterruptedException {
        Thread thread = new Thread(() -> {
            for (int i = 0; i < 1000; i++) {
                increment();
            }
        });
        Thread thread2 = new Thread(() -> {
            for (int i = 0; i < 1000; i++) {
                increment();
            }
        });
        thread.start();
        thread2.start();
        thread.join();
        thread2.join();
        System.out.println("Count: " + count.get().getValue());
    }
}

Count: 1927 失败 上面出现问题是因为AtomicReference只关心myInt的值,但值得内部与AtomicReference无关

class myInt {
    private volatile int value;

    public myInt(int value) {
        this.value = value;
    }

    public void increment() {
        value++;
    }

    public int getValue() {
        return value;
    }
}

public class AtomicIntegerDemo {
    private static AtomicReference<myInt> count = new AtomicReference<>(new myInt(0));

    public static void increment() {
        count.updateAndGet(myInt -> new myInt(myInt.getValue() + 1));
    }

Count: 2000

数组的原子操作类AtomicIntegerArray

上面只提供了单个元素的原子类,如果需要使用数组,则可以使用AtomicIntegerArray;当然更常见的是使用CopyOnWriteArrayList;这个类的全部方法都是native方法,目前我还不会阅读:(

有序性与可见性

private final int[] array; // 没有在array上面实现,但对于单个元素应该实现了

为甚不用CopyOnWriteArrayList呢?

AtomicDouble

本人本想去寻找有关Double的原子类,Java内也有DoubleAdder这些类,不过目前并没有详细阅读DoubleAdder,只是发现内部依然使用的long存储具体值;目前由两种思路实现double的原子类

  1. 使用AtomicReference去包装
  2. 使用AtomicLong模拟

正好在stack overflow上找到了例子,仅供参考

stackoverflow.com/questions/5…

同时在google的utils包内也找到了double的原子类 guava.dev/releases/19…

结尾

更新字段的原子操作类

如果我们只需要某个类里的某个字段,那么就需要使用原子更新字段类,Atomic包提供了以下三个类:

  • AtomicIntegerFieldUpdater:  原子更新整型的字段的更新器。通过静态工厂方法创建newUpdater(Class<U> tclass, String fieldName)
  • AtomicLongFieldUpdater:  原子更新长整型字段的更新器。通过静态工厂方法创建newUpdater(Class<U> tclass, String fieldName)
  • AtomicReferenceFieldUpdater 原子更新引用类型里的字段。比前两个方法的范围要广,可以更新任意类型的字段,通过静态的工厂方法创建 newUpdater(Class<U> tclass, Class<W> vclass, String fieldName)

www.cnblogs.com/jinggod/p/8…

感觉认识还不足,暂时只能写出这么多