深入理解并发编程- 原子类

本文为读书笔记
原子类也不一定是线程安全的

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原理

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);
    }
}

在这里插入图片描述
原因:
方法是线程安全的,但是方法之间无法保证线程安全,还是得添加同步语句

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值