首先看一下JUC的位置:
rt.jar 包下 java.util.concurrent
atomic使用的是cas的更新方式,当某个线程在执行atomic的方法时,不会被其他线程打断,而别的线程就像自旋锁一样,一直等到该方法执行完成,才由JVM从等待队列中选择一个线程执行,在软件层面上是非阻塞的,它是在底层硬件上借助处理器的原子指令来保证的。
1.5时
atomic包下面总共有12个类。根据起作用可以分为四类
原子基本类型
使用原子方式的基本类型 | |
AtomicBoolean | 布尔原子类 |
AtomicInteger | 整形原子类 |
AtomicLong | 长整型原子类 |
AtomicReference | 对象V原子类 |
由于三种类的方法基本一样,下面就以AtomicInteger 为例:
public final int set() //设一个值
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) //如果当前值等于预期值,则以原子方式将该值设置为输入值(update)
public final void lazySet(int newValue) //最终设置为newValue,使用 lazySet 设置之后可能导致其他线程在之后的一小段时间内还是可以读到旧的值。
原子数组
使用原子的方式的数组 | |
AtomicIntegerArray | 整形数组原子类 |
AtomicLongArray | 长整形数组原子类 |
AtomicReferenceArray | 引用类型数组原子类 (即对应数组中存放的元素为对象形式) |
由于三种类的方法基本一样,下面就以AtomicIntegerArray 为例:
public final int get(int i) //获取 index=i 位置元素的值
public final int set(int i, int newValue) //为 index=i 位置元素设新值
public final int getAndSet(int i, int newValue) //返回 index=i 位置的当前的值,并将其设置为新值:newValue
public final int getAndIncrement(int i) //获取 index=i 位置元素的值,并让该位置的元素自增
public final int getAndDecrement(int i) //获取 index=i 位置元素的值,并让该位置的元素自减
public final int getAndAdd(int i, int delta) //获取 index=i 位置元素的值,并加上预期的值
boolean compareAndSet(int i, int expect, int update) //如果index=i 位置的值等于预期值,则以原子方式将 index=i 位置的元素值设置为输入值(update)
public final void lazySet(int i, int newValue) //最终 将index=i 位置的元素设置为newValue,使用 lazySet 设置之后可能导致其他线程在之后的一小段时间内还是可以读到旧的值。
原子更新字段
原子更新引用 | |
AtomicIntegerFieldUpdater | 原子更新整形字段的更新器 |
AtomicLongFieldUpdater | 原子更新长整形字段的更新器 |
AtomicReferenceFieldUpdater | 原子更新引用类型字段的更新器 |
这里值得注意的是:使用AtomicIntegerFieldUpdater.newUpdater修改属性时:
1)被修改的属性必须是volatile类型的,在线程之间共享变量时保证立即可见,
2)属性的修饰符(public/protected/default/private)要保证当前操作对该属性可以直接进行,比如当我们用
3)private volatile int age 时就会报错,因为private修饰时,外部无法访问也无法修改。
4)只能是实例变量,不能是类变量,也就是说不能加static关键字。
5)只能是可修改变量,不能使final变量,因为final的语义就是不可修改。
对于AtomicIntegerFieldUpdater和AtomicLongFieldUpdater只能修改int/long类型的字段,不能修改其包装类型(Integer/Long)。如果要修改包装类型就需要使用AtomicReferenceFieldUpdater。
原子更新引用
原子更新引用 | |
AtomicMarkableReference | 与AtomicStampedReference差不多,只不过第二个参数不是用的int作为标志,而用boolean类型做标记 |
AtomicStampedReference | AtomicReference的扩展版,增加了一个参数stamp标记,这里是为了解决了AtomicInteger和AtomicLong的操作会出现ABA问题 |
ABA问题:简单讲就是多线程环境,2次读写中一个线程修改A->B,然后又B->A,另一个线程看到的值未改变,又继续修改成自己的期望值。当然我们如果不关心过程,只关心结果,那么这个就是无所谓的ABA问题。
为了解决ABA问题,JUC提供了AtomicMarkableReference/AtomicStampedReference类.
AtomicStampedReference是利用版本戳的形式记录了每次改变以后的版本号,这样的话就不会存在ABA问题了,在这里我借鉴一下别人举得例子
public class AtomicStampedReferenceTest {
public static void main(String[] args) throws InterruptedException{
final Integer init_Ref = 0, init_Stamp = 0;
AtomicStampedReference<Integer> asr = new AtomicStampedReference<>(init_Ref,init_Stamp);
System.out.println("init_Ref为:"+asr.getReference() + " ====== init_Stamp为:"+asr.getStamp());
Thread t1 = new Thread(new Runnable() {
@Override
public void run() {
Integer ref = asr.getReference();
Integer stamp = asr.getStamp();
//与前面AtomicReference的compareAndSet不同的是,增加了一个stamp标记比较,ref与stamp同时与
// 当前的ref、stamp相同时才进行 + 操作
System.out.println(ref + " ====== " + stamp + " ====== "
+ asr.compareAndSet(ref, ref + 10, stamp, stamp + 1));
}
});
Thread t2 = new Thread(new Runnable() {
@Override
public void run() {
Integer ref = asr.getReference();
// 当前的ref相同,但此时版本号不同,操作不执行返回false
System.out.println(ref + " ====== " + init_Stamp + " ====== "
+ asr.compareAndSet(ref, ref + 10, init_Stamp, init_Stamp + 1));
}
});
t1.start();
t1.join(); //只是为了让代码有序执行
t2.start();
t2.join();
System.out.println("最终的结果为:"+ asr.getReference() + " ====== " + asr.getStamp());
}
}
以上参考: https://blog.csdn.net/weixin_43849277/article/details/108377793
1.8以后
新增:
Striped64
DoubleAccumulator
LongAccumulator
DoubleAdder
LongAdder
是JDK1.8新增的部分,是对AtomicLong等类的改进。
比如LongAccumulator与LongAdder在高并发环境下比AtomicLong更高效。
API中是这么介绍的:
LongAdder中会维护一组(一个或多个)变量,这些变量加起来就是要以原子方式更新的long型变量。当更新方法add(long)在线程间竞争时,该组变量可以动态增长以减缓竞争。方法sum()返回当前在维持总和的变量上的总和。与AtomicLong相比,LongAdder更多地用于收集统计数据,而不是细粒度的同步控制。在低并发环境下,两者性能很相似。但在高并发环境下,LongAdder有着明显更高的吞吐量,但是有着更高的空间复杂度。
LongAdder是jdk1.8提供的累加器,基于Striped64实现。它常用于状态采集、统计等场景。AtomicLong也可以用于这种场景,但在线程竞争激烈的情况下,LongAdder要比AtomicLong拥有更高的吞吐量,但会耗费更多的内存空间。
LongAccumulator和LongAdder类似,也基于Striped64实现。但要比LongAdder更加灵活(要传入一个函数接口),LongAdder相当于是LongAccumulator的一种特例。