java juc 多线程自增, LongAdder 对象,比 AtomicLong 性能更好

多线程自增

    • 在多线程环境下,如何解决变量的内存不可见问题以及线程安全问题。
    • LongAdder 如何实现自增
      • 实现原理呢?

能和你相遇,除了幸运,我想不出别的词,而被爱,则是荣幸

volatile 解决多线程内存不可见问题。对于一写多读,是可以解决变量同步问题,
但是如果多写,同样无法解决线程安全问题。如果是 count++操作,使用如下类实现:
AtomicInteger count = new AtomicInteger(); count.addAndGet(1); 如果是 JDK8,推
荐使用 LongAdder 对象,比 AtomicLong 性能更好(减少乐观锁的重试次数)

在多线程环境下,如何解决变量的内存不可见问题以及线程安全问题。

  1. 内存不可见问题

在多线程环境下,由于每个线程都有自己的缓存,因此一个线程修改的变量值可能不会立即同步到主内存中,而其他线程在读取该变量时可能读取到的是旧的值,导致内存不可见问题。

为了解决内存不可见问题,可以使用 volatile 关键字修饰变量,使得变量的值修改后能够立即同步到主内存中。这样,在读取变量时就能够获取到最新的值,避免了内存不可见问题。

  1. 线程安全问题

除了内存不可见问题,多线程环境下还可能出现线程安全问题,例如多个线程同时对同一个变量进行写操作,可能会出现竞态条件(Race Condition)和数据不一致等问题。为了解决线程安全问题,可以使用各种线程安全的类和方法,例如 synchronized、ReentrantLock、AtomicInteger、AtomicLong 等。

对于一些读多写少的变量,可以使用 Atomic 类型的对象来解决线程安全问题。例如,在多个线程中对 count++ 进行操作时,可以使用 AtomicInteger 对象来实现:

AtomicInteger count = new AtomicInteger();
count.addAndGet(1);

这样可以保证 count 的操作是原子性的,避免了竞态条件和数据不一致等问题。

在 JDK8 中,可以使用 LongAdder 对象来替代 AtomicLong 来实现更好的性能。LongAdder 在高并发情况下可以减少乐观锁的重试次数,从而提高性能。

为什么 可以使用 LongAdder 对象来替代 AtomicLong 来实现更好的性能

LongAdder 是 JDK8 中新增的一种线程安全的累加器(accumulator)类型,它可以用来实现类似于 AtomicLong 的累加操作。相比于 AtomicLong,LongAdder 在高并发情况下可以实现更好的性能,


原因如下:

减少竞争, 减少 CAS 操作

AtomicLong 中的实现方式是采用 CAS(Compare And Swap)算法,即通过循环比较和交换的方式来保证操作的原子性。而在高并发情况下,多个线程可能会竞争同一个 AtomicLong 对象,从而导致竞争激烈,CAS 操作的失败率增加,进而导致性能下降。

而 LongAdder 的实现方式则是采用分段锁的方式,将一个 LongAdder 对象分成多个 Cell,每个 Cell 维护一个独立的计数器。在多线程进行累加操作时,线程会根据自己的 hash 值选择对应的 Cell 进行累加操作,从而减少了竞争,提高了并发性能。

综上所述,LongAdder 在高并发情况下可以实现更好的性能,原因是它采用了分段锁的方式来减少竞争,以及减少 CAS 操作,从而提高了并发性能。

LongAdder 如何实现自增

在实际使用中,我们可以通过调用 LongAdder 对象的 increment() 方法来实现自增操作。例如:

LongAdder adder = new LongAdder();
adder.increment(); // 自增1

在上面的示例中,我们创建了一个 LongAdder 对象,并通过调用 increment() 方法来实现自增操作。

LongAdder 的自增操作主要是通过内部的 Cell 数组来实现的。具体来说,LongAdder 内部维护了一个 Cell 数组,每个 Cell 维护了一个独立的计数器。在进行自增操作时,LongAdder 首先会根据当前线程的 hash 值选择对应的 Cell,然后对该 Cell 的计数器进行自增操作。如果当前线程的 hash 值对应的 Cell 已经被占用了,则 LongAdder 会尝试分配一个新的 Cell,并将新的 Cell 加入到 Cell 数组中。

实现原理呢?

首先,我们来看 LongAdder 的内部实现,LongAdder 内部维护了一个 Cell 数组,每个 Cell 维护了一个独立的计数器。在进行自增操作时,LongAdder 会根据当前线程的 hash 值选择对应的 Cell,然后对该 Cell 的计数器进行自增操作。如果当前线程的 hash 值对应的 Cell 已经被占用了,则 LongAdder 会尝试分配一个新的 Cell,并将新的 Cell 加入到 Cell 数组中。

具体来说,在 LongAdder 中,Cell 是一个内部类,定义如下:

static final class Cell {
    volatile long value;
    Cell(long x) { value = x; }
}

在 Cell 中,value 属性是一个 volatile long 类型的变量,用于保存计数器的值。这里使用 volatile 关键字修饰 value 变量,保证了多线程访问该变量时的可见性和有序性。

然后,我们来看 LongAdder 的 increment() 方法实现,该方法的源码如下:

public void increment() {
    Cell[] as;
    long b, v;
    int m;
    Cell a;
    if ((as = cells) != null || !casBase(b = base, b + 1L)) {
        boolean uncontended = true;
        if (as == null || (m = as.length - 1) < 0 ||
            (a = as[getProbe() & m]) == null ||
            !(uncontended = a.cas(v = a.value, v + 1L)))
            longAccumulate(1L, null, uncontended);
    }
}

在 increment() 方法中,首先判断当前 LongAdder 是否有 Cell 数组,如果有 Cell 数组,则先进行 Cell 数组的操作。否则,直接对 base 进行自增操作。

在进行 Cell 数组的操作时,increment() 方法会根据当前线程的 hash 值选择对应的 Cell,然后对该 Cell 的计数器进行自增操作。如果当前线程的 hash 值对应的 Cell 已经被占用了,则 LongAdder 会尝试分配一个新的 Cell,并将新的 Cell 加入到 Cell 数组中。

具体来说,在 Cell 数组中进行自增操作时,increment() 方法首先根据当前线程的 hash 值计算出需要操作的 Cell 的索引,然后尝试使用 CAS(Compare And Swap)操作来对该 Cell 的计数器进行自增操作。如果 CAS 操作成功,则直接返回;否则,increment() 方法会调用 longAccumulate() 方法来进行累加操作,该方法的实现类似于 LongAdder 的实现。

综上所述,LongAdder 的 increment() 方法主要是通过 Cell 数组和 CAS 操作来实现的。在进行自增操作时,increment() 方法会根据当前线程的 hash 值选择对应的 Cell,然后对该 Cell 的计数器进行自增操作。如果当前线程的 hash 值对应的 Cell 已经被占用了,则 LongAdder 会尝试分配一个新的 Cell,并将新的 Cell 加入到 Cell 数组中。如果 CAS 操作失败,则会调用 longAccumulate() 方法来进行累加操作。由于 LongAdder 在高并发情况下可以减少竞争和 CAS 操作,因此相比于 AtomicLong,它可以实现更好的性能。

需要注意的是,在进行高并发情况下的累加操作时,可能会出现 Cell 数组的扩容操作,从而导致竞争更加激烈,进而影响性能。因此,在实际使用中,需要根据实际情况选择合适的 LongAdder 对象数量,以及合适的并发度,从而达到最优的性能。

你可能感兴趣的:(java基础,java,算法,自增,LongAddr,AtomicInteger)