JUC CAS算法(java)

i++的原子性问题

i++是非原子操作,当Thread1进入并打印i的值,此时还未进行i++,Thread1将CPU让出来,然后Thread2进入继续执行打印i,然后执行i++,此时打印两次执行一次i++,显然结果是不对的。

非原子操作代码如下:线程不安全

JUC CAS算法(java)_第1张图片

JUC CAS算法(java)_第2张图片

改进:使用原子变量 AtomicInteger atomicInteger = new AtomicInteger(0);

JUC CAS算法(java)_第3张图片

JUC CAS算法(java)_第4张图片

 JDK1.5 以后, java.util.concurrent.atomic包下,提供了常用的原子变量,

         原子变量中的值,使用volatile 修饰,保证了内存可见性;
         CAS(Compare-And-Swap) 算法保证数据的原子性;

CAS算法

  • CAS(Compare-And-Swap) 算法是硬件对于并发的支持,针对多处理器操作而设计的处理器中的一种特殊指令,用于管理对共享数据的并发访问;

  • CAS 是一种无锁的非阻塞算法(属于乐观锁)的实现;

  • CAS 包含了三个操作数:

    • 进行比较的旧预估值: A

    • 需要读写的内存值: V

    • 将写入的更新值: B

    • 当且仅当 A == V 时, V = B, 否则,将不做任何操作,并且这个比较交换过程属于原子操作;

应用:

  • 原子操作类,例如AtomicInteger,AtomicBoolean …
  • 适用于并发量较小,多cpu情况下;Java中有许多线程安全类,比如线程安全的集合类。从Java5开始,在java.util.concurrent包下提供了大量支持高效并发访问的集合接口和实现类。如:ConcurrentMap、ConcurrentLinkedQueue等线程安全集合。

相关概念:

乐观锁:总是认为是线程安全的,不怕别的线程修改变量,如果修改了我就再重新尝试。
悲观锁:总是认为线程不安全,不管什么情况都进行加锁,要是获取锁失败,就阻塞。

优点:
这个算法相对synchronized是比较“乐观的”,它不会像synchronized一样,当一个线程访问共享数据的时候,别的线程都在阻塞。synchronized不管是否有线程冲突都会进行加锁。由于CAS是非阻塞的,它死锁问题天生免疫,并且线程间的相互影响也非常小,更重要的是,使用无锁的方式完全没有锁竞争带来的系统开销,也没有线程间频繁调度带来的开销,所以它要比锁的方式拥有更优越的性能。

CAS算法的内存如下:

先用旧值A(从num表中取出的值)判断是否和当前num值V相同,如果不同,不做任何操作。如果相同,则将新值B赋值给num;

JUC CAS算法(java)_第5张图片

案例:模拟CAS算法

JUC CAS算法(java)_第6张图片

JUC CAS算法(java)_第7张图片

ABA问题参考juc.mdCAS算法

代码如下:

JUC CAS算法(java)_第8张图片

CAS缺点

  • 循环时间太长;
  • 只能保证一个共享变量原子操作;
  • 会出现ABA问题;

ABA问题:

在CAS算法中,需要取出内存中某时刻的数据(由用户完成),在下一时刻比较并替换(由CPU完成,该操作是原子的)。这个时间差中,会导致数据的变化。
假设如下事件序列:
线程 1 从内存位置V中取出A。
线程 2 从位置V中取出A。
线程 2 进行了一些操作,将B写入位置V。
线程 2 将A再次写入位置V。
线程 1 进行CAS操作,发现位置V中仍然是A,操作成功。

尽管线程 1 的CAS操作成功,但不代表这个过程没有问题——对于线程 1 ,线程 2 的修改已经丢失。

你可能感兴趣的:(Java第一阶段学习总结)