无锁并发演进背景
随着系统高并发的压力越来越大,传统同步机制在高并发场景下的性能瓶颈和缺点可能会逐渐显露:
(1)性能损耗:synchronized等锁机制会导致线程阻塞和上下文切换,在高并发场景下性能损耗显著。
(2)锁粒度粗:锁通常需要保护整个代码块或方法,而实际可能只需保护单个变量的操作。
(3)易用性差:ReentrantLock等锁,开发者需手动管理锁的获取与释放,易引发死锁或锁竞争。
现代CPU提供原子指令(如CAS),允许无锁实现线程安全操作,原子类由此诞生,原子类基于CAS实现非阻塞同步,避免线程阻塞。原子类是Java为平衡性能与线程安全在并发编程领域的重要演进,其设计初衷是提供更轻量、高效的同步方案。接下来我们就对java中的原子类进行学习
原子类的基本概念
原子类(Atomic Classes)之所以被称为“原子类”,源于其操作具备不可分割性的原子特性。原子类的方法(如incrementAndGet()、compareAndSet())能够确保在多线程环境下,对共享变量的操作要么完全执行,要么完全不执行,调用这些方法就像调用一个加了synchronized的方法一样,不会被其他线程干扰或观察到中间状态。
jdk1.5之后引入了原子类,Java并发包(java.util.concurrent.atomic)中的原子类的类名均以Atomic前缀标识(如AtomicInteger),明确其线程安全且不可分割的操作语义,开发者一眼就可以理解其用途。
核心原子类详解
从上图可以看出,int、long、boolean这三种数据类型提供了对应的AtomicInteger、AtomicLong、AtomicBoolean原子类型,这三个是核心的原子类型。下面我们来详细学习下这三个原子类的核心api方法。
认识AtomicInteger类
/*** An {@code int} value that may be updated atomically. See the* {@link VarHandle} specification for descriptions of the properties* of atomic accesses. An {@code AtomicInteger} is used in* applications such as atomically incremented counters, and cannot be* used as a replacement for an {@link java.lang.Integer}. However,* this class does extend {@code Number} to allow uniform access by* tools and utilities that deal with numerically-based classes.** @since 1.5* @author Doug Lea*/
public class AtomicInteger extends Number implements java.io.Serializable {private static final long serialVersionUID = 6214790243416807050L;
...
}
一个可以原子更新的整数值,常用于需要原子递增的计数器等场景,但不能作为java.lang.Integer的替代品。不过,该类继承了Number,以便工具和实用程序能够以统一的方式处理基于数值的类。(Number是java.lang包中的抽象类,作为所有数值包装类的父类,提供统一的数值转换接口)。
初始化与值操作方法
(1)AtomicInteger(int initialValue)
/*** Creates a new AtomicInteger with the given initial value.** @param initialValue the initial value*/
public AtomicInteger(int initialValue) {value = initialValue;
}/*** Creates a new AtomicInteger with initial value {@code 0}.*/
public AtomicInteger() {
}
提供了两个创建AtomicInteger对象的构造函数,可以根据给定的整型值创建AtomicInteger对象,或者创建初始值为0的AtomicInteger对象。
(2)set(int newValue)
/*** Sets the value to {@code newValue},* with memory effects as specified by {@link VarHandle#setVolatile}.** @param newValue the new value*/
public final void set(int newValue) {value = newValue;
}
调用该方法强制更新AtomicInteger对象的值(立即可见)。
(3)lazySet(int newValue)
/*** Sets the value to {@code newValue},* with memory effects as specified by {@link VarHandle#setRelease}.** @param newValue the new value* @since 1.6*/
public final void lazySet(int newValue) {U.putIntRelease(this, VALUE, newValue);
}
调用该方法会延迟更新AtomicInteger对象的值(不保证立即可见)。
(4)int get()
/*** Returns the current value,* with memory effects as specified by {@link VarHandle#getVolatile}.** @return the current value*/
public final int get() {return value;
}
调用该方法会返回AtomicInteger对象的值。
原子增减方法
(1)getAndIncrement()
/*** Atomically increments the current value,* with memory effects as specified by {@link VarHandle#getAndAdd}.** <p>Equivalent to {@code getAndAdd(1)}.** @return the previous value*/
public final int getAndIncrement() {return U.getAndAddInt(this, VALUE, 1);
}
调用该方法会返回AtomicInteger对象旧的值,然后加1。
(2)incrementAndGet()
/*** Atomically increments the current value,* with memory effects as specified by {@link VarHandle#getAndAdd}.** <p>Equivalent to {@code addAndGet(1)}.** @return the updated value*/
public final int incrementAndGet() {return U.getAndAddInt(this, VALUE, 1) + 1;
}
调用该方法会对AtomicInteger对象的值加1并返回加1后的值。
(3)getAndDecrement()
/*** Atomically decrements the current value,* with memory effects as specified by {@link VarHandle#getAndAdd}.** <p>Equivalent to {@code getAndAdd(-1)}.** @return the previous value*/
public final int getAndDecrement() {return U.getAndAddInt(this, VALUE, -1);
}
调用该方法会返回AtomicInteger对象旧的值,然后减1。
(4)decrementAndGet()
/*** Atomically decrements the current value,* with memory effects as specified by {@link VarHandle#getAndAdd}.** <p>Equivalent to {@code addAndGet(-1)}.** @return the updated value*/
public final int decrementAndGet() {return U.getAndAddInt(this, VALUE, -1) - 1;
}
调用该方法会对AtomicInteger对象的值减1并返回减1后的值。
(5)getAndAdd(int delta)
/*** Atomically adds the given value to the current value,* with memory effects as specified by {@link VarHandle#getAndAdd}.** @param delta the value to add* @return the previous value*/
public final int getAndAdd(int delta) {return U.getAndAddInt(this, VALUE, delta);
}
调用该方法会返回AtomicInteger对象旧的值,然后加指定的值delta。
(6)addAndGet(int delta)
/*** Atomically adds the given value to the current value,* with memory effects as specified by {@link VarHandle#getAndAdd}.** @param delta the value to add* @return the updated value*/
public final int addAndGet(int delta) {return U.getAndAddInt(this, VALUE, delta) + delta;
}
调用该方法会对AtomicInteger对象加指定的值delta,然后返回增加后的值。
比较并交换(Compare-And-Swap,CAS)方法
(1)compareAndSet(int expectedValue, int newValue)
/**
* Atomically sets the value to {@code newValue}
* if the current value {@code == expectedValue},
* with memory effects as specified by {@link VarHandle#compareAndSet}.
*
* @param expectedValue the expected value
* @param newValue the new value
* @return {@code true} if successful. False return indicates that
* the actual value was not equal to the expected value.
*/
public final boolean compareAndSet(int expectedValue, int newValue) {return U.compareAndSetInt(this, VALUE, expectedValue, newValue);
}
这是一个最常见最核心的方法。调用该方法传入两个参数,expectedValue表示期望的值,newValue是新的值。将AtomicInteger对象当前的值与expectedValue比较,如果相等就替换为新的值newValue,这就是“比较并交换”的逻辑。
典型应用场景
AtomicInteger的典型应用场景包括计数器,如统计在线用户数;修改状态标志,如原子修改布尔状态(需配合AtomicBoolean);生成唯一ID等。下面我们以计数器为例,举例说明AtomicInteger在保证线程安全的同时,性能显著优于synchronized锁方案。
场景举例
定义一个计数器,然后创建100个线程,每个线程对原子整型对象加10000,100个线程操作后原子整型对象的值应该是100*10000=1000000。通过synchronized加锁方案对计数器操作和直接使用AtomicInteger类型的计数器无锁操作两种方式实现,对比两种方式的结果和耗时。
代码实现
(1)synchronized加锁操作计数器
public class SynchronizedCounter {//基本整型对象private static int counter = 0;//锁对象private static final Object lock = new Object();public static void main(String[] args) throws InterruptedException {long start = System.currentTimeMillis();//创建100个线程Thread[] threads = new Thread[100];for (int i = 0; i < 100; i++) {threads[i] = new Thread(() -> {//每个线程对原子整型对象加10000for (int j = 0; j < 10000; j++) {synchronized (lock) {counter++;}}});threads[i].start();}//等待100个线程操作完成for (Thread t : threads) {t.join();}System.out.println("counter Result: " + counter);System.out.println("Cost Time: " + (System.currentTimeMillis() - start) + "ms");}
}
运行结果:
counter Result: 1000000
Cost Time: 233ms
(2)使用AtomicInteger类型的计数器无锁操作
import java.util.concurrent.atomic.AtomicInteger;public class AtomicCounter {//原子整型对象private static AtomicInteger counter = new AtomicInteger(0);public static void main(String[] args) throws InterruptedException {long start = System.currentTimeMillis();//创建100个线程Thread[] threads = new Thread[100];for (int i = 0; i < 100; i++) {threads[i] = new Thread(() -> {//每个线程对原子整型对象加10000for (int j = 0; j < 10000; j++) {counter.incrementAndGet();}});threads[i].start();}//等待100个线程操作完成for (Thread t : threads) {t.join();}System.out.println("Result: " + counter.get());System.out.println("Time: " + (System.currentTimeMillis() - start) + "ms");}
}
运行结果:
counter Result: 1000000
Time: 97ms
从运行结果可以看出,使用AtomicInteger类型的计数器无锁操作,耗时从233ms下降到97ms。
总结
文章最后以表格形式总结AtomicInteger的核心方法,涵盖其功能、行为描述及典型应用场景
方法分类 | 方法名 | 行为描述 | 示例(初始值=5) | 应用场景 |
---|---|---|---|---|
基础操作 | AtomicInteger(int initialValue) | 构造指定初始值的原子整数 | new AtomicInteger(5) | 初始化计数器 |
set(int newValue) | 强制更新值(立即可见) | atomicInt.set(10) | 重置计数器 | |
lazySet(int newValue) | 延迟更新(不保证立即可见) | atomicInt.lazySet(10) | 低可见性要求场景 | |
get() | 获取当前值 | int val = atomicInt.get() | 读取当前状态 | |
原子增减 | getAndIncrement() | 返回旧值,然后+1 | 返回5,值变为6 | 生成序列号 |
incrementAndGet() | +1后返回新值 | 返回6,值变为6 | 高并发计数 | |
getAndDecrement() | 返回旧值,然后-1 | 返回5,值变为4 | 资源释放计数 | |
decrementAndGet() | -1后返回新值 | 返回4,值变为4 | 实时状态更新 | |
getAndAdd(int delta) | 返回旧值,然后增加delta | 返回5,值变为10(delta=5) | 批量操作 | |
addAndGet(int delta) | 增加delta 后返回新值 | 返回10,值变为10 | 累加计算 | |
CAS操作 | compareAndSet(int expect, int update) | 若当前值等于expect ,则更新为update ,返回是否成功 | atomicInt.compareAndSet(5, 10) | 无锁同步 |