(JUC简介)
转自 极客时间
1. JUC简介
从JDK1.5起,Java API 中提供了java.util.concurrent(简称JUC)包,在此包中定义了并发编程中很常用 的工具,比如:线程池、阻塞队列、同步器、原子类等等。JUC是 JSR 166 标准规范的一个实现,JSR 166 以及 JUC 包的作者是同一个人 Doug Lea 。
2. 原子类与CAS
通过上面学习volatile,我们发现volatile修饰的变量存在原子性的BUG,这个问题怎么解决呢?难道只能使用Synchronized吗?
2.1 Atomic包
java.util.concurrent.atomic包 从JDK 1.5开始提供了java.util.concurrent.atomic包(以下简称Atomic包),这个包中的原子操作类提 供了一种用法简单、性能高效、线程安全地更新一个变量的方式。可以解决volatile原子性操作变量的问 题。
因为变量的类型有很多种,所以在Atomic包里一共提供了13个类,属于4种类型的原子更新方式,分别 是原子更新基本类型、原子更新数组、原子更新引用和原子更新属性(字段)。Atomic包里的类基本都 是使用Unsafe实现的包装类。
AtomicInteger主要API如下:
get() //直接返回值
getAndAdd(int) //增加指定的数据,返回变化前的数据
getAndDecrement() //减少1,返回减少前的数据
getAndIncrement() //增加1,返回增加前的数据
getAndset(int) ////增加1,返回增加前的数据
addAndGet(int) //设置指定的数据,返回设置前的数据
decrementAndGet() //减少1,返回减少后的值
incrementAndGet() //增加1,返回增加后的值
1azyset(int) //仅仅当get时才会set
compareAndset(int,int) //尝试新增后对比,若增加成功则返回true否则返回false
用AtomicInteger解决可见性案例中的问题
2.2 CAS介绍
2.3 CAS原理详解
Java中对CAS的实现
Java不能像C/C++那样直接操作内存区域,需要通过本地方法(native 方法)来访问。JAVA中的==CAS操作都 是通过sun包下Unsafe类实现==,而==Unsafe类中的方法都是native方法==。
Unsafe类,全限定名是sun.misc.Unsafe,位于在 sun.misc 包下,不属于Java 标准API。 Unsafe对CAS操作的实现有三个
/**
* Atomically update Java variable to x if it is currently holding expected.
* @return true if successful
*/
public final native boolean compareAndSwapInt(Object o, long offset, int expected, int x);
/**
* Atomically update Java variable to x if it is currently holding expected.
* @return true if successful
*/
public final native boolean compareAndSwapObject(Object o, long offset,Object expected,Object x);
/**
* Atomically update Java variable to x if it is currently holding expected.
* @return true if successful
*/
public final native boolean compareAndSwapLong(Object o, long offset, long expected, long x);
我们以其中的compareAndSwapInt为例,来说明
compareAndSwapInt
- 方法作用:如果当前时刻,待更新的原值 与 预期值 expected相等,则将 待更新的原值 的 值更新 为x。如果更新成功,则返回 true,否则返回 false。
- compareAndSwapInt是 Unsafe 类中提供的一个原子操作。方法一共有四个参数: 1.o:需要改变的对象 2.offset:内存偏移量,offset 为o对象所属类中,某个属性在类中的内存地址偏移量 3.expected :预期值 4.x:拟替换的新值
内存偏移量offset的作用是什么?
- 计算出对象中,待更新的原值的准确内存地址
- Java对象在内存中会占用一段内存区域,Java对象的属性会按照一定的顺序在对象内存中存储。根 据对象this就可以定位到this对象在内存的起始地址,然后在根据属性state(相对this)的offset内存 偏移量,就可以精确的定位到state的内存地址,从而得到当前时刻state在内存中的值。
2.4 CAS缺陷
CAS虽然高效地解决了原子操作,但是还是存在一些缺陷的,主要表现在三个地方:循环时间太长、只能保证一个共享变量原子操作、ABA问题。
- 循环时间太长:如果CAS一直不成功呢?如果自旋CAS长时间地不成功,则会给CPU带来非常大的开销。 原子类AtomicInteger#getAndIncrement()的方法
- 只能保证一个共享变量原子操作:看了CAS的实现就知道这只能针对一个共享变量,如果是多个共 享变量就只能使用锁了。
- ABA问题:CAS需要检查操作值有没有发生改变,如果没有发生改变则更新。但是存在这样一种情 况:如果一个值原来是A,变成了B,然后又变成了A,那么在CAS检查的时候会发现没有改变,但 是实质上它已经发生了改变,这就是所谓的ABA问题。对于ABA问题其解决方案是加上版本号,即 在每个变量绑定一个版本号,每次改变时加1,即A —> B —> A,变成1A —> 2B —> 3A。
下面我们将通过一个例子可以可以看到AtomicStampedReference和AtomicInteger的区别。我们定义 两个线程,线程1负责将100 —> 101 —> 100,线程2执行 100 —>2022,看两者之间的区别。 运行结果充分展示了AtomicInteger的ABA问题和AtomicStampedReference解决ABA问题。
标签:JUC,Java,CAS,原子,并发,int,内存,expected From: https://blog.51cto.com/u_15323027/9585904