Java多线程之原子操作类

本文目录:
文章目录包的使用
在并发编程中很容易出现并发安全问题,最简单的例子就是多线程更新变量i=1,多个线程执行i++操作,就有可能获取不到正确的值,而这个问题,最常用的方法是通过进行控制来达到线程安全的目的 。但是由于是采用的是悲观锁策略,并不是特别高效的一种解决方案 。实际上,在J.U.C下的包提供了一系列的操作简单,性能高效,并能保证线程安全的类去更新多种类型 。包下的这些类都是采用乐观锁策略CAS来更新数据 。CAS原理与问题
CAS操作(又称为无锁操作)是一种乐观锁策略 。它假设所有线程访问共享资源的时候不会出现冲突,因此不会阻塞其他线程的操作 。那么,如果出现冲突了怎么办?无锁操作是使用CAS( and swap)来鉴别线程是否出现冲突,出现冲突就重试当前操作直到没有冲突为止 。
CAS的操作过程
举例说明:
包中的类,是通过类下的函数自旋来保证原子性,
其中函数调用的函数如下所示:
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;}
CAS有3个操作数,内存值V,旧的预期值A,要修改的新值B 。当且仅当预期值A和内存值V相同时,将内存值V修改为B,否则什么都不做 。
可见只有自旋实现更新数据操作之后,while循环才能够结束 。
CAS的问题 自旋时间过长 。由函数可知,自旋时间过长会对性能是很大的消耗 。ABA问题 。因为CAS会检查旧值有没有变化,这里存在这样一个有意思的问题 。比如一个旧值A变为了成B,然后再变成A,刚好在做CAS时检查发现旧值并没有变化依然为A,但是实际上的确发生了变化 。解决方案可以添加一个版本号可以解决 。原来的变化路径A->B->A就变成了1A->2B->3C,或使用ce工具类 。包的使用 原子更新基本类型
包中原子更新基本类型的工具类:
:以原子更新的方式更新;
:以原子更新的方式更新;
:以原子更新的方式更新Long;
这几个类的用法基本一致,这里以为例总结常用的方法
(int delta):以原子方式将输入的数值与实例中原本的值相加,并返回最后的结果;() :以原子的方式将实例中的原值进行加1操作,并返回最终相加后的结果;(int ):将实例中的值更新为新值,并返回旧值;():以原子的方式将实例中的原值加1,返回的是自增前的旧值;
原理不再赘述,参考上文函数 。
使用示例:
public class AtomicExample {private static AtomicInteger atomicInteger = new AtomicInteger(2);public static void main(String[] args) {System.out.println(atomicInteger.getAndIncrement());System.out.println(atomicInteger.incrementAndGet());System.out.println(atomicInteger.get());}}// 2 4 4
为了解决自旋导致的性能问题,JDK8在包中推出了类 。采用的方法是,共享热点数据分离的计数:将一个数字的值拆分为一个数组 。不同线程会命中到数组的不同槽中,各个线程只对自己槽中的那个值进行CAS操作,这样热点就被分散了,冲突的概率就小很多;要得到这个数字的话,就要把这个值加起来 。相比,并发量大大提高 。
优点:有很高性能的并发写的能力
缺点:读取的性能不是很高效,而且如果读取的时候出现并发写的话,结果可能不是正确的
原子更新数组类型
包中提供能原子更新数组中元素的工具类:
:原子更新整型数组中的元素;
:原子更新长整型数组中的元素;
:原子更新引用类型数组中的元素
这几个类的用法一致,就以来总结下常用的方法: