深入探讨AtomicLong

发布时间:2023-05-20

在多线程编程中,要确保数据的可见性和正确性,最常见的方式是用锁来保证同步,但是锁会带来额外的开销,如频繁的拷贝、线程挂起与恢复等,而AtomicLong则是一种无锁的同步实现方式,能够保证线程安全,同时性能也非常不错。

一、AtomicLong就是原子操作

AtomicLongjava.util.concurrent.atomic包下的一个类,它对一个长整型值进行原子操作,同时保证在多线程环境下的线程安全。在Java中,原子操作是一种不可分割、不可中断的操作,只有执行完毕或者不执行。 就是说:要么成功执行整个操作,要么就不执行,而不会出现执行了一半就被中断的情况,这对于高并发场景下的数据修改和读取是非常有用的。

二、AtomicLong的使用方法

AtomicLong类的常用方法主要有三种:

1. 原子更新基本类型

public final long getAndAdd(long delta)
public final long addAndGet(long delta)
public final long getAndIncrement()
public final long getAndDecrement()
public final long getAndSet(long newValue)

这五个方法都是原子性地对一个long类型的变量进行更新。其中:

  • getAndAdd()方法是先获得当前值,再增加指定的值;
  • addAndGet()方法相反,是先增加指定的值,再获得新的值;
  • getAndIncrement()方法是先获得当前值,再自增;
  • getAndDecrement()方法是先获得当前值,再自减;
  • getAndSet()方法是先获得当前值,再设置新值。

2. 原子更新数组

public final int getAndAdd(int[] array, int index, int delta)
public final int addAndGet(int[] array, int index, int delta)
public final int getAndIncrement(int[] array, int index)
public final int getAndDecrement(int[] array, int index)
public final int getAndSet(int[] array, int index, int newValue)

这五个方法都是原子性地对一个指定下标的整型数组的元素进行更新,其他操作和上面的方法相似。

3. 原子更新:compareAndSet

public final boolean compareAndSet(long expect, long update)

compareAndSet()方法是一个CAS操作,即比较并交换。如果当前值和期望值相等,则更新它为新值,否则不进行任何操作。

三、AtomicLong的例子

1. 使用AtomicLong进行线程安全的计数器操作

public class Counter {
    private AtomicLong value = new AtomicLong(0L);
    public long getValue() {
        return value.get();
    }
    public void increment() {
        value.incrementAndGet();
    }
    public void add(long delta) {
        value.addAndGet(delta);
    }
    public void decrement() {
        value.decrementAndGet();
    }
}

在这个例子中,我们定义了一个计数器类,用于统计某一事件发生的次数。通过使用AtomicLong,我们保证了该计数器是线程安全的。可以看到,我们只需要调用AtomicLong类中提供的方法,就可以完成加减等操作。

2. 使用compareAndSet()方法进行锁的释放

public class SpinLock {
    private AtomicBoolean locked = new AtomicBoolean(false);
    public void lock() {
        boolean flag = false;
        while (!(flag = locked.compareAndSet(false, true))) {
            // 换成尝试去获取锁的代码
        }
    }
    public void unlock() {
        locked.set(false);
    }
}

在这个例子中,我们定义了一个自旋锁SpinLock,用于保护临界区代码的执行顺序。当线程需要进入临界区时,先调用lock()方法获取锁,如果锁被其他线程占用,则自旋等待。当线程执行完临界区代码后,调用unlock()方法释放锁,让其他线程可以获取锁。

四、总结

在多线程编程中,为了保证线程安全,一般需要使用锁等同步机制。但是,锁会带来额外的开销,影响程序的性能。而AtomicLong则是一种无锁的线程安全实现方式,它能够保证线程安全,并且性能非常出色。在实际应用中,可以根据需求使用AtomicLong的各种方法,也可以根据自己的需求实现自己的原子类。无论何种方式,只要遵循了原子操作的规则,就能够保证线程安全。