1、 原子类简介
原子类作用和锁类似,是为了保证线程安全,但是原子类相比于锁:
- 粒度更细:原子变量可以把竞争范围缩小到变量级别,通常情况下,锁的粒度都要大于原子变量的粒度。
- 效率更高:除了高度竞争的情况之外,使用原子类的效率通常会比使用同步互斥锁的效率更高,因为原子类底层利用了 CAS 操作,不会阻塞线程。
2、 六类原子类概览
类型 | 具体类 |
---|---|
Atomic* 基本类型原子类 | AtomicInteger、AtomicLong、AtomicBoolean |
Atomic*Array 数组类型原子类 | AtomicIntegerArray、AtomicLongArray、AtomicReferenceArray |
Atomic*Reference 引用类型原子类 | AtomicReference、AtomicStampedReference、AtomicMarkableReference |
Atomic*FieldUpdater 升级类型原子类 | AtomicIntegerfieldupdater、AtomicLongFieldUpdater、AtomicReferenceFieldUpdater |
Adder 累加器 | LongAdder、DoubleAdder |
Accumulator 积累器 | LongAccumulator、DoubleAccumulator |
2.1、 基本类型原子类
2.1.1、AtomicInteger 类常用方法
- public final int get() //获取当前的值
- public final int getAndSet(int newValue) //获取当前的值,并设置新的值
- public final int getAndIncrement() //获取当前的值,并自增
- public final int getAndDecrement() //获取当前的值,并自减
- public final int getAndAdd(int delta) //获取当前的值,并加上预期的值
- boolean compareAndSet(int expect, int update) //如果输入的数值等于预期值,则以原子方式将该值更新为输入值
2.1.2、AtomicInteger 示例
public class AtomicIntegerDemo implements Runnable{
private static final AtomicInteger atomicInteger = new AtomicInteger();
private static Integer basicInteger = 0;
public void addAtomic(){
atomicInteger.incrementAndGet();
}
public void addBasic(){
basicInteger ++;
}
@Override
public void run() {
IntStream.range(0,1000).forEach(e -> {
addAtomic();
addBasic();
});
}
public static void main(String[] args) throws InterruptedException {
Runnable runnable = new AtomicIntegerDemo();
Thread thread1 = new Thread(runnable);
Thread thread2 = new Thread(runnable);
thread1.start();
thread2.start();
thread1.join();
thread2.join();
System.out.println("原子类结果:" + atomicInteger.get());
System.out.println("普通类结果:" + basicInteger);
}
}
输出
原子类结果:2000
普通类结果:1437
2.2、数组型原子类
即数组中每个对象都保证原子特性
2.3、 引用类型原子类
AtomicReference 类的作用和AtomicInteger 并没有本质区别, AtomicInteger 可以让一个整数保证原子性,而AtomicReference 可以让一个对象保证原子性
public class SpinLock {
private AtomicReference<Thread> sign = new AtomicReference<>();
public void lock(){
Thread current = Thread.currentThread();
while(!sign.compareAndSet(null, current)){
System.out.println("自旋尝试获取锁");
}
}
public void unlock(){
Thread current = Thread.currentThread();
sign.compareAndSet(current, null);
}
public static void main(String[] args) {
SpinLock spinLock = new SpinLock();
Runnable runnable = new Runnable() {
@Override
public void run() {
System.out.println(Thread.currentThread().getName() + "尝试获取自旋锁");
spinLock.lock();
System.out.println(Thread.currentThread().getName() + "成功获取自旋锁");
try {
Thread.sleep(300);
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
spinLock.unlock();
System.out.println(Thread.currentThread().getName() + "释放自旋锁");
}
}
};
new Thread(runnable).start();
new Thread(runnable).start();
}
}
2.4、升级类型原子类
AtomicIntegerFieldUpdater对普通变量升级
使用场景:偶尔需要一个原子Get-Set操作
public class AtomicUpdater implements Runnable{
private static CountHandler countHandlerOne;
private static CountHandler countHandlerTwo;
private static AtomicIntegerFieldUpdater<CountHandler> countUpdater =
AtomicIntegerFieldUpdater.newUpdater(CountHandler.class,"count");
@Override
public void run() {
IntStream.range(0,1000).forEach(e -> {
countHandlerOne.count ++;
countUpdater.getAndIncrement(countHandlerTwo);
});
}
@Data
static class CountHandler{
//不能是包装类 Integer
volatile int count;
}
public static void main(String[] args) throws InterruptedException {
countHandlerOne = new CountHandler();
countHandlerTwo = new CountHandler();
Runnable runnable = new AtomicUpdater();
Thread thread1 = new Thread(runnable);
Thread thread2 = new Thread(runnable);
thread1.start();
thread2.start();
thread1.join();
thread2.join();
System.out.println("普通int结果:" + countHandlerOne.count);
System.out.println("升级原子类结果:" + countHandlerTwo.count);
}
输出
普通int结果:1918
升级原子类结果:2000
2.5、Adder 加法器
2.5.1、LongAdder和AtomicLong对比
- 在并发程度不高时,LongAdder和AtomicLong性能差不多,高并发下 LongAdder 比 AtomicLong 效率更高,但是需要耗费更多空间。
- LongAdder适合的场景时统计求和技术的场景,而LongAdder只提供了基本的add方法,AtomicLong 还具有CAS方法
对比LongAdder和AtomicLong耗时,发现高并发下 LongAdder 比 AtomicLong 效率更高
public class LongAdderTest {
static class TaskLongAdder implements Runnable{
private LongAdder counter;
public TaskLongAdder(LongAdder counter) {
this.counter = counter;
}
@Override
public void run() {
IntStream.range(0,10000).forEach(e -> {
counter.increment();
});
}
}
static class TaskAtomicLong implements Runnable{
private AtomicLong counter;
public TaskAtomicLong(AtomicLong counter) {
this.counter = counter;
}
@Override
public void run() {
IntStream.range(0,10000).forEach(e -> {
counter.getAndIncrement();
});
}
}
public static void main(String[] args) {
LongAdder longAdder = new LongAdder();
TaskLongAdder taskLongAdder = new TaskLongAdder(longAdder);
ExecutorService executorService1 = Executors.newFixedThreadPool(20);
Long start1 = System.currentTimeMillis();
IntStream.range(0,2000).forEach(e-> executorService1.execute(taskLongAdder));
executorService1.shutdown();
while (!executorService1.isTerminated()){
}
Long end1 = System.currentTimeMillis();
System.out.println("LongAdder耗时:" + (end1 - start1) + " 结果: " + longAdder.sum());
AtomicLong atomicLong = new AtomicLong();
TaskAtomicLong taskAtomicLong = new TaskAtomicLong(atomicLong);
ExecutorService executorService2 = Executors.newFixedThreadPool(20);
Long start2 = System.currentTimeMillis();
IntStream.range(0,2000).forEach(e-> executorService2.execute(taskAtomicLong));
executorService2.shutdown();
while (!executorService2.isTerminated()){
}
Long end2 = System.currentTimeMillis();
System.out.println("AtomicLong耗时:" + (end2 - start2) + " 结果: " + atomicLong.get());
}
}
输出
LongAdder耗时:137 结果: 20000000
AtomicLong耗时:367 结果: 20000000
2.5.2、AtomicLong和LongAdder原理刨析
AtomicLong原理
AtomicLong每次加法都要flush和refresh,导致浪费资源
LongAdder原理
LongAdder 引入了分段累加的概念,内部一共有两个参数参与计数:第一个叫作 base,它是一个变量,第二个是 Cell[] ,是一个数组。其中的 base 是用在竞争不激烈的情况下的,可以直接把累加结果改到 base 变量上。当竞争激烈时,各个线程会分散累加到自己所对应的那个 Cell[] 数组的某一个对象。LongAdder 会通过计算出每个线程的 hash 值来给线程分配到不同的 Cell 上去
执行 LongAdder.sum() 的时候,会把各个线程里的 Cell 累计求和,并加上 base,形成最终的总和
public long sum() {
Cell[] as = cells; Cell a;
long sum = base;
if (as != null) {
for (int i = 0; i < as.length; ++i) {
if ((a = as[i]) != null)
sum += a.value;
}
}
return sum;
}
2.6、Accumulator 积累器
2.6.1、简介
Accumulator 和Adder非常相似,Accumulator 就是一个更通用版本的Adder
public class LongAccumulatorDemo {
public static void main(String[] args) {
LongAccumulator accumulator = new LongAccumulator((x,y) -> x * y,1);
ExecutorService executorService = Executors.newFixedThreadPool(20);
IntStream.range(1,10).forEach(e-> executorService.execute(() -> accumulator.accumulate(e)));
executorService.shutdown();
while (!executorService.isTerminated()){
}
System.out.println("结果: " + accumulator.getThenReset());
}
}
输出
结果: 362880
2.6.2、适合场景
- 适合数据量大时的多线程并行计算
- 计算的顺序不会影响结果