本文为读书笔记
原子类也不一定是线程安全的
文章目录
1. 原子更新基本类型类
- AtomicBoolean:原子更新布尔类型。
- AtomicInteger:原子更新整型。
- AtomicLong:原子更新长整型。
以AtomicInteger举例:
详细可查jdk文档
void lazySet(int newValue):最终会设置成newValue,使用lazySet设置值后,可能导致其他线程在之后的一小段时间内还是可以读到旧的值。
UnSafe
Unsafe只提供了3种CAS方法:compareAndSwapObject、compare-AndSwapInt和compareAndSwapLong ,那么如何实现其他的类型呢?
再看AtomicBoolean源码,发现它是先把Boolean转换成整型,再使用compareAndSwapInt进行CAS,所以原子更新char、float和double变量也可以用类似的思路来实现。
比较重要的Unsafe类的方法:
/*
第一个参数 修改对象 一般传入this
第二个参数 字段偏移量
第三个参数 except 期望旧值
第四个藏书 update except与当前相同,则修改的值
*/
public final native boolean compareAndSwapInt(Object var1, long var2, int var4, int var5);
public final int getAndAddInt(Object var1, long var2, int var4) {
int var5;
do {
var5 = this.getIntVolatile(var1, var2);
} while(!this.compareAndSwapInt(var1, var2, var5, var5 + var4));
return var5;
}
Unsafe的获取:
Field theUnsafe = Unsafe.class.getDeclaredField("theUnsafe");
theUnsafe.setAccessible(true);
unsafe = (Unsafe) theUnsafe.get(null);
2. 原子更新数组
- AtomicIntegerArray:原子更新整型数组里的元素。
- AtomicLongArray:原子更新长整型数组里的元素。
- AtomicReferenceArray:原子更新引用类型数组里的元素。
以AtomicIntegerArray为例:
需要注意的是,数组value通过构造方法传递进去,然后AtomicIntegerArray会将当前数组复制一份,所以当AtomicIntegerArray对内部的数组元素进行修改时,不会影响传入的数组。
3. 原子更新引用类型
- AtomicReference:原子更新引用类型。
- AtomicReferenceFieldUpdater:原子更新引用类型里的字段。
- AtomicMarkableReference:原子更新带有标记位的引用类型。可以原子更新一个布尔类型的标记位和引用类型。
以AtomicReference为例:
4. 原子更新字段类
- AtomicIntegerFieldUpdater:原子更新整型的字段的更新器。
- AtomicLongFieldUpdater:原子更新长整型字段的更新器。
- AtomicStampedReference:原子更新带有版本号的引用类型。该类将整数值与引用关联起来,可用于原子的更新数据和数据的版本号,可以解决使用CAS进行原子更新时可能出现的ABA问题。
什么是ABA问题
假设初始值是1,线程A和线程B拿到1,线程A改为了2,线程A又改为了1 ,线程B
是不会知道线程A把它这个数修改过的,此事线程B再将 1改为2,是能够成功的,
在某些场景下这样的情况是存在问题的! 假如基于这个数另外还有其他的字段会同步修改就会出现问题
要想原子地更新字段类需要两步。
第一步,因为原子更新字段类都是抽象类,每次使用的时候必须使用静态方法newUpdater()创建一个更新器,并且需要设置想要更新的类和属性。
第二步,更新类的字段(属性)必须使用public volatile修饰符。
5. LongAdder
一个或多个变量一起维持初始为零long总和。 当更新(方法add(long) )跨线程竞争时,变量集可以动态增长以减少争用。 方法sum() (或等效地, longValue() )返回保持总和的整个变量组合的当前总和。
这个类是通常优于选择AtomicLong,当多个线程更新时使用,用于诸如收集统计信息,不用于细粒度同步控制的共同总和。 在低更新竞争下,这两类具有相似的特征。 但是,在高度竞争的情况下,这一类的预期吞吐量明显高于牺牲更高的空间消耗。
LongAdders可以使用ConcurrentHashMap来维护可扩展的频率映射(一种直方图或多集)。 例如,要向ConcurrentHashMap<String,LongAdder> freqs添加一个计数,如果尚未存在,则可以使用freqs.computeIfAbsent(k -> new LongAdder()).increment();
LongAdder的基本思路就是分散热点,将value值分散到一个数组中,不同线程会命中到数组的不同槽中,各个线程只对自己槽中的那个值进行CAS操作,这样热点就被分散了,冲突的概率就小很多。如果要获取真正的long值,只要将各个槽中的变量值累加返回。
LongAdder原理
base变量:非竞态条件下,直接累加到该变量上
Cell[]数组:竞态条件下,累加个各个线程自己的槽Cell[i]中
最终结果的计算是下面这个形式:
LongAdder类与AtomicLong类的区别在于高并发时前者将对单一变量的CAS操作分散为对数组cells中多个元素的CAS操作,取值时进行求和;而在并发较低时仅对base变量进行CAS操作,与AtomicLong类原理相同。不得不说这种分布式的设计还是很巧妙的。
6. AtomicFieldUpdater
在Java的原子包中提供了原子性操作对象属性的解决方案。在该解决方案中,开发者无须使用synchronized关键字对共享数据的操作进行同步,也无须将对应的数据类型声明成原子类型。
- AtomicIntegerFieldUpdater:原子性地更新对象的int类型属性,该属性无须被声明成AtomicInteger。
- AtomicLongFieldUpdater:原子性地更新对象的long类型属性,该属性无须被声明成AtomicLong。
- AtomicReferenceFieldUpdater:原子性地更新对象的引用类型属性,该属性无须被声明成AtomicReference<\T>。
使用例子:
public class AtomicFieldUpdaterDemo {
public static void main(String[] args) {
AtomicIntegerFieldUpdater<Alex> updater = AtomicIntegerFieldUpdater.newUpdater(Alex.class, "salary");
Alex alex = new Alex();
int result = updater.addAndGet(alex, 1);
System.out.println(result == 1);
}
static class Alex {
volatile int salary;
public int getSalary() {
return this.salary;
}
}
}
需要注意的是:
- 未被volatile关键字修饰的成员属性无法被原子性地更新
- 类变量无法被原子性地更新
- 无法直接访问的成员属性不支持原子性地更新
- final修饰的成员属性无法被原子性地更新
- 父类的成员属性无法被原子性地更新
7. 原子类不能保证线程安全
public class AtomicIntegerUnsafe {
public static void main(String[] args) {
ExecutorService executorService = Executors.newFixedThreadPool(5);
Sync sync = new Sync();
executorService.execute(sync);
executorService.execute(sync);
executorService.execute(sync);
executorService.execute(sync);
executorService.execute(sync);
executorService.shutdown();
}
}
class Sync implements Runnable {
private AtomicInteger count = new AtomicInteger(0);
public void run() {
System.out.println("\tthreadName->" + Thread.currentThread().getName() + ",加了100后的值是" + count.addAndGet(100));
count.addAndGet(1);
}
}
原因:
方法是线程安全的,但是方法之间无法保证线程安全,还是得添加同步语句