线程安全-并发随记

在多线程开发中,我们常遇到的问题就是并发数据,怎么保证线程安全、怎么保证数据不重复。

1. volatile

volatile是一个java关键字,常用于在多线程中共享变量

volatile原理

每个thread都拥有自己的线程存储空间,并且什么时候将存储空间的数据同步到主内存中是不确定的。

volatile并不会给变量加上锁,性能上要比synchronized好,volatile可以保证线程在读取数据的时候都是直接读取内存(在线程A修改了变量的值后,会将变量写回到主内存中,同时其他线程会去主内存中获取变量的值)

volatile的优缺点

  • 线程并发中保证变量的可见性(当这个变量发生变化时,对其他线程来说是可见的,线程中缓存的变量失效,线程获取到的永远是最新修改的值)
  • volatile修饰的变量不允许线程内部cache缓存和重排序,保证了有序性
  • volatile只能保证每次读取或修改时的原子性,i++ 不能保证原子性(只能保证单次的读取/修改原子性)

2. CAS (compare And Swap)

CAS 根据名字可以知道,先比较在交换值,jdk1.5后开始引入,通过和内存中的旧值比较,相同的话则修改成新的值

Compare And Swap原理

CAS指令执行时,只有当内存地址和修改值一致的时候才会将内存中的值修改为想要修改的值,否则则直接返回最新值不会做任何操作,但是会时不时的重试,直到更新了为止(自旋)。正因为这一点,CAS即使没有锁,也能及时发现其他线程对当前变量的改变。

CAS通过JNI(java本地调用)来实现,利用unsafe实现原子性操作(unsafe系统硬件级原子性操作,要么一起成功要么一起失败)

Compare And Swap的优缺点

优点:

  • 不需要借助同步锁实现了线程之间的数据共享(依赖volatile将变量暴露)
  • 从思想来说,synchronized属于悲观锁,悲观的认为并发情况严重,死死抓住资源不放;而CAS属于乐观锁,乐观的认为并发情况不怎么严重,再比较内存中的值不一致会反复重试

缺点

  • CPU开销比较大,正因为CAS的自旋机制,会导致一个线程反复的尝试更新某个的值,带给CPU很大的压力
  • 只能保证某个变量的原子性不能保证代码块的原子性
  • ABA问题
ABA举证:
场景:假设ATM机底层用CAS实现的存取钱操作
小明账户还有100元,在ATM机上准备取出50元,假如ATM机bug或者鼓掌,小明触发了2次取钱操作;
这个时候如果只是触发了2次也没关系,在第一个请求处理时,根据CAS原理,对比账户余额和实际账户余额是一致的都是100,此时账户剩余50,并发的第二次取钱操作根据原理肯定是不会执行,但是会阻塞等待重试;这个时候是对账户没有任何影响的。
再假设如果在小明取钱的同时,小明家里给小明打了50元钱;这个时候还会是这样的情况?
在打钱线程执行的时候,发现账户余额期望值50与账户一致,打钱成功,账户余额:100;这还没完,
第2次取钱操作此时重试,发现期望值100与余额一致,余额扣款成功;

小明的账户经历了: 100(A) --> 50(B) --> 100(A) --> 50

avatar

ABA解决方法:在比较期望值和内存值的时候在增加比对内存地址值的版本号是否一致,线程每次在变更内存地址值的时候都会刷新版本。可以通过AtomicStampedReference类来实现版本的比较

public boolean compareAndSet(V   expectedReference,
                                 V   newReference,
                                 int expectedStamp,
                                 int newStamp) {
        Pair<V> current = pair;
        return
            expectedReference == current.reference &&
            expectedStamp == current.stamp &&
            ((newReference == current.reference &&
              newStamp == current.stamp) ||
             casPair(current, Pair.of(newReference, newStamp)));
    }

CAS的应用

在java.util.concurrent并发包很多类都有运用到CAS机制

  • AQS(AbstractQueuedSynchronizer),阻塞锁及一系列FIFO等待队列的框架,它底层都依赖对state这个变量做原子性操作来实现同步,修改state就有用到CAS等等…
protected final boolean compareAndSetState(int expect, int update) {
        // See below for intrinsics setup to support this
        return unsafe.compareAndSwapInt(this, stateOffset, expect, update);
    }
  • AtomicInteger、AtomicBoolean等都可以通过compareAndSet来更新值

版权声明:本文为hetangyuese原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。
本文链接:https://www.cnblogs.com/hetangyuese/p/11655398.html