目录
- JUC介绍
- atomic原子类框架
- Unsafe类与CAS
- CAS的问题
- 总结——原子包实现原理:volatile+循环CAS
- 1. 基本类型原子类:AtomicInteger
- 总结——实现原理:Volatile关键字+CAS
- 2. 数组类型原子类:AtomicIntegerArray
- 总结——实现原理:Final数组+Volatile语义+CAS
- 3. 引用类型原子类:AtomicReference
- 总结——实现原理:Volatile关键字+CAS
- 4. 字段类型原子类:AtomicIntegerFieldUpdater
- 总结——实现原理:反射+Volatile关键字+CAS
如果不使用原子类,要同步修改Integer等变量就需要加锁,atomic原子类就是解决这种问题。
其原理概括来说就是volatile关键字/final关键字+循环CAS尝试。
可以说是一种乐观锁的理念。
JUC介绍
JUC,即 java.util.concurrent 包的缩写,是java原生的并发包和一些常用的工具类。
它主要包括了以下几个功能:
- atomic原子类
- collections集合
- lock锁
- executor执行器
atomic原子类框架
如果不使用JUC中的原子类,那么如果我们要并发地改变Integer等数值,则需要使用锁来控制。而使用java.util.concurrent中的atomic包,则避免了锁的使用,保证线程安全。
- atomic如何 不加锁保证线程安全 ?
答案是CAS(compare and swap)或者说它是实现了一种乐观锁。
实现CAS的类:Unsafe工具类。
Unsafe类与CAS
Unsafe类,顾名思义是一个不安全的类,它提供了一系列不安全的操作方法,这个类只能被一些信任的代码中使用。其主要功能内容包括了:内存操作、CAS、Class相关、对象操作、线程调度、系统信息获取、内存屏障、数组操作等。
- unsafe对象的获取
private static final Unsafe theUnsafe;
@CallerSensitive
public static Unsafe getUnsafe() {
Class var0 = Reflection.getCallerClass();
if (!VM.isSystemDomainLoader(var0.getClassLoader())) {
throw new SecurityException("Unsafe");
} else {
return theUnsafe;
}
}
限制了调用此方法的类加载器必须是BootStrap ClassLoader,也就是启动类加载器:负责将存放在<JAVA_HOME>\lib目录中的,或者是被-Xbootclasspath参数所指定的路径中的类库加载到虚拟机中,如rt.jar。这也是其「只能被一些信任的代码中使用」的含义。
- 其提供的CAS方法
public final native boolean compareAndSwapObject(Object var1, long var2, Object var4, Object var5);
public final native boolean compareAndSwapInt(Object var1, long var2, int var4, int var5);
public final native boolean compareAndSwapLong(Object var1, long var2, long var4, long var6);
以compareAndSwapInt为例,参数:
- Object var1:需要修改的对象
- long var2:需要修改的字段到对象头的偏移量
- int var4:期望的值
- int var5:需要更新到的值
这些CAS方法都是native方法,CAS的本质是靠硬件实现的,是 原子性指令 ,JVM只是封装了汇编调用。
CAS指令的3个操作数:
- 内存地址V
- 旧的预期值A(上一次从内存中读取的值)
- 要修改的新值B
将内存地址为V的值与A比较,如果相等,说明这段时间没有其他的线程修改此值,将内存V值swap为B;否则说明了地址为V的值被更新过了,什么都不做。
CAS的问题
使用CAS虽然省去了加锁的上下文切换开销,但也存在着一些问题:
- 循环时间长时,将一直处于自旋状态,CPU开销大
- 只能保证对一个共享变量的原子操作
- ABA问题
总结——原子包实现原理:volatile+循环CAS
可以理解为:
- CAS本来是计算机指令层面的一个原子命令,在java中提供了列实现它的原生接口;
- 而java中的volatile关键字又保证了不同线程的可见性,为线程之间的通信做了基础;
把这两个特性结合在一起,就是concurrent包实现的基础。往下看会发现Java中一些原子类的实现,大致的模式都是:
- 声明 共享变量为volatile 修饰,保证线程间可见性;
- 使用 循环+CAS 原子性来更新值;
1. 基本类型原子类:AtomicInteger
- 常见方法的使用:
AtomicInteger ai = new AtomicInteger(0);
System.out.println(ai.addAndGet(2)); //addAndGet:先增加2再返回(2)
System.out.println(ai.getAndAdd(2)); //getAndAdd:先返回原值再增加2(2)
System.out.println(ai.get()); //get:返回值(4)
- AtomicInteger类中的变量:
public class AtomicInteger extends Number implements java.io.Serializable {
private static final long serialVersionUID = 6214790243416807050L;
// setup to use Unsafe.compareAndSwapInt for updates
private static final Unsafe unsafe = Unsafe.getUnsafe();
private static final long valueOffset;
static {
try {
valueOffset = unsafe.objectFieldOffset
(AtomicInteger.class.getDeclaredField("value"));
} catch (Exception ex) { throw new Error(ex); }
}
private volatile int value;
}
- value:当前整型值
注意AtomicInteger中的值是 用volatile关键字修饰 的,保证了一个线程修改其值之后对其他线程可见。 - valueOffset:字段value的内存偏移地址
通过字段valueOffset的值可以定位到AtomicInteger对象中value的内存地址。
- 方法实现
public final int addAndGet(int delta) {
return unsafe.getAndAddInt(this, valueOffset, delta) + delta;
}
public final int getAndAdd(int delta) {
return unsafe.getAndAddInt(this, valueOffset, delta);
}
到了unsafe类中:
public final int getAndAddInt(Object o, long offset, int delta) {
int v;
do {
v = getIntVolatile(o, offset);
} while (!compareAndSwapInt(o, offset, v, v + delta));
return v;
}
- getIntVolatile(o, offset)——意义为 从主存中获取 此被volatile修饰的int值
(参数o:atomicInteger对象;参数offset:内存偏移量) - compareAndSwapInt(o, offset, v, v + delta)——即CAS
(参数v:原始值;参数v + delta:目标值)
使用while循环实现一个乐观锁,失败时将进入自旋
总结——实现原理:Volatile关键字+CAS
2. 数组类型原子类:AtomicIntegerArray
- 常见方法的使用
//初始化原子数组
//方式1:传入已经创建好的int数组参数
int[] array = {1, 2, 3};
AtomicIntegerArray air = new AtomicIntegerArray(array);
//方式2:传入数组长度,默认各个元素值为0,再逐个赋值
AtomicIntegerArray air2 = new AtomicIntegerArray(3);
air2.set(0, 1);
air2.set(1, 2);
air2.set(2, 3);
//给第一个元素+2
air.getAndAdd(0, 2);
- AtomicIntegerArray类中的变量
public class AtomicIntegerArray implements java.io.Serializable {
private static final Unsafe unsafe = Unsafe.getUnsafe();
private static final int base = unsafe.arrayBaseOffset(int[].class);
private static final int shift;
private final int[] array;
static {
int scale = unsafe.arrayIndexScale(int[].class);
if ((scale & (scale - 1)) != 0)
throw new Error("data type scale not a power of two");
shift = 31 - Integer.numberOfLeadingZeros(scale);
}
}
- int[] array:通过int[]数组来保存要操作的数组;
- base:数组中第一个元素相对于数组起始位置的偏移值;
- shift:每个int元素内存中的位置;
注意与AtomicInteger不同的是,array数组并没有用volatile来修饰,而是使用了 final关键字 。这样这个数组将被存放在方法区,方法区中的内容是线程共享的,所以同样可以保证多线程的可见性。
- 方法实现
public final int getAndAdd(int i, int delta) {
return unsafe.getAndAddInt(array, checkedByteOffset(i), delta);
}
//返回第i个元素的内存偏移量
private long checkedByteOffset(int i) {
if (i < 0 || i >= array.length)
throw new IndexOutOfBoundsException("index " + i);
return byteOffset(i);
}
可知对于AtomicIntegerArray中元素的原子操作,是通过传入一个数组的index值,本质还是到了unsafe中的CAS,原理相同。
Java中的数组,在多线程中只能被当做一个整体,所以在数组的原子操作类中,也是将对数组的操作转化为对单个元素的操作,来保证整个数组的原子性。
- 为什么不使用volatile修饰数组?
用volatile修饰的数组,其中的每个元素的读写并不具有volatile的特性。 - 为什么使用final修饰数组仍然具有volatile的效果?
用final修饰数组,保证了数组在使用时已经被初始化,且不能指向其他对象;而get、set方法的实现(通过unsafe类的putIntVolatile、getIntVolatile实现)保证了对于数组中每个元素的操作是有volatile特性的。
代码如下:
//方式1的数组赋值:
public AtomicIntegerArray(int[] array) {
// Visibility guaranteed by final field guarantees
this.array = array.clone();
}
//方式2的数组赋值:
public AtomicIntegerArray(int length) {
array = new int[length];
}
public final void set(int i, int newValue) {
unsafe.putIntVolatile(array, checkedByteOffset(i), newValue);
}
//get方法:
public final int get(int i) {
return getRaw(checkedByteOffset(i));
}
private int getRaw(long offset) {
return unsafe.getIntVolatile(array, offset);
}
- 这里有个问题:方式2的数组赋值,使用的是set方法,通过unsafe类的putIntVolatile保证元素的可见性;
但方式1,直接通过array.clone()来初始化数组,并没有指定每个元素为volatile,如何保证可见性?
首先这里的可见性指:当元素被赋值后立即对其他线程可见。
在方式1中,假如出现了其他线程不可见的情况,必然是有一个线程A正在进行构造函数,另一个线程B正在进行数组的读操作,可以模拟为:
//线程A
public FinalArray() { //构造函数
this.array = {1, 2, 3}; //1
}
//线程B
public void reader() {
int i = this.array[i];//2
}
与上文的volatile定义的重排序规则类似,final关键字也有一些指令重排序规则:
- 在构造函数内对一个final引用对象的成员域(代码中的array)的写入,与随后在构造函数外把这个被构造对象的引用赋值给一个引用变量(代码中获取array[i]),这两个操作之间不能重排序。
即线程A与B并行执行,A-1代码必然在B-2之前,也就是array元素对其他的线程也是可见的。
假如此时又有一个线程C,也是写操作:
//线程C
public void set() {
this.array[0] = 8; //3
}
结果是final可以保证A-1代码必然在B-2之前,却不能保证C-3在B-2之前。如果要确保读线程B看到写线程C对数组元素的写入,那么需要使用同步原语(lock或者volatile)来实现。而AtomicIntegerArray也确实是这么实现的。
总结——实现原理:Final数组+Volatile语义+CAS
3. 引用类型原子类:AtomicReference
假设一种场景,我们不光是要原子性地修改某个数值,而是要原子性地更新多个值。此时就需要原子对象引用。
- 常见方法使用
@Data
@AllArgsConstructor
class Reference {
private String val1;
private String val2;
}
public static void main(String[] args) {
Reference reference = new Reference("old1", "old2");
AtomicReference<Reference> atomicReference = new AtomicReference<Reference>(reference);
while (true) {
Reference referenceOld = atomicReference.get();
Reference referenceNew = new Reference("new1", "new2");
if (atomicReference.compareAndSet(referenceOld, referenceNew)) {
reference.setVal1(referenceNew.getVal1());
reference.setVal2(referenceNew.getVal2());
break;
}
}
System.out.println(reference.getVal1());
System.out.println(reference.getVal2());
}
- AtomicReference类中的变量
public class AtomicReference<V> implements java.io.Serializable {
private static final long serialVersionUID = -1848883965231344442L;
private static final Unsafe unsafe = Unsafe.getUnsafe();
private static final long valueOffset;
static {
try {
valueOffset = unsafe.objectFieldOffset
(AtomicReference.class.getDeclaredField("value"));
} catch (Exception ex) { throw new Error(ex); }
}
private volatile V value;
/**
* Creates a new AtomicReference with the given initial value.
*
* @param initialValue the initial value
*/
public AtomicReference(V initialValue) {
value = initialValue;
}
}
- value:保存了一个 volatile修饰 的value值,即目标对象
- 方法实现
/**
* Atomically sets the value to the given updated value
* if the current value {@code ==} the expected value.
* @param expect the expected value
* @param update the new value
* @return {@code true} if successful. False return indicates that
* the actual value was not equal to the expected value.
*/
public final boolean compareAndSet(V expect, V update) {
return unsafe.compareAndSwapObject(this, valueOffset, expect, update);
}
unsafe类中通过CAS比较两个对象是否相等,注意是"=="比较,意味着比较内存地址。
总结——实现原理:Volatile关键字+CAS
4. 字段类型原子类:AtomicIntegerFieldUpdater
- 使用场景
假设一种场景,在一个类中我们只需要原子地修改其中的一个变量,如何实现?
class People {
private Integer age;
private String name;
public People(Integer age) {
this.age = age;
}
public Integer getAge() {
return age;
}
public void setAge(Integer age) {
this.age = age;
}
public String getName() {
return name;
}
public void setName(String name) {
= name;
}
public void increAge() {
this.age++;
}
}
public class TestAtomicInterFieldUpdater {
public static void main(String[] args) {
People people = new People(0);
for (int i = 0; i < 1000; i++) {
new Thread(() -> {
people.increAge();
}).start();
}
Thread.sleep(200);
System.out.println(people.getAge());
}
}
这段代码运行的结果并不全都是1000,要保证age字段的原子性,首先是使用synchronized关键字:
public synchronized void increAge() {
this.age++;
}
如果想使用更轻量级的原子类呢,首先想到的就是AtomicInteger的使用,代码会被修改为这样:
class People {
// private Integer age;
private AtomicInteger age;
private String name;
public People(Integer age) {
// this.age = age;
this.age = new AtomicInteger(age);
}
public Integer getAge() {
// return age;
return age.get();
}
public void setAge(Integer age) {
// this.age = age;
this.age.set(age);
}
public String getName() {
return name;
}
public void setName(String name) {
= name;
}
public void increAge() {
// this.age++;
this.age.incrementAndGet();
}
}
public class TestAtomicInterFieldUpdater {
public static void main(String[] args) {
People people = new People(0);
for (int i = 0; i < 1000; i++) {
new Thread(() -> {
people.increAge();
}).start();
}
Thread.sleep(200);
System.out.println(people.getAge());
}
}
还有另外一种实现方式,就是JUC中给我们提供的原子类:AtomicIntegerFieldUpdater。它可以原子性地修改对象的属性。相对比与AtomicInteger的形式,它对于原有类的修改更小,更符合开发中的开闭原则。也许在对遗留的代码进行修改时比较适合使用这种方式吧。
class People {
//private Integer age;
protected volatile int age;
private String name;
public People(Integer age) {
this.age = age;
}
public Integer getAge() {
return age;
}
public void setAge(Integer age) {
this.age = age;
}
public String getName() {
return name;
}
public void setName(String name) {
= name;
}
public void increAge() {
this.age++;
}
}
class AtomicPeoeple extends People {
private AtomicIntegerFieldUpdater<People> atomicAge = AtomicIntegerFieldUpdater.newUpdater(People.class, "age");
public AtomicPeoeple(Integer age) {
super(age);
}
@Override
public void increAge() {
this.atomicAge.getAndIncrement(this);
}
}
public class TestAtomicInterFieldUpdater {
public static void main(String[] args) throws InterruptedException {
AtomicPeoeple people = new AtomicPeoeple(0);
for (int i = 0; i < 1000; i++) {
new Thread(() -> {
people.increAge();
}).start();
}
Thread.sleep(200);
System.out.println(people.getAge());
}
}
注意到原子属性age的修饰改为了:protected volatile int age;
- AtomicIntegerFieldUpdater对象的创建
AtomicIntegerFieldUpdater.newUpdater(People.class, "age")
public static <U> AtomicIntegerFieldUpdater<U> newUpdater(Class<U> tclass,
String fieldName) {
return new AtomicIntegerFieldUpdaterImpl<U>
(tclass, fieldName, Reflection.getCallerClass());
}
构造参数:
- tclass:拥有目标属性的类
- fieldName:要进行原子性操作的目标属性
- caller:调用类
private static final class AtomicIntegerFieldUpdaterImpl<T>
extends AtomicIntegerFieldUpdater<T> {
private static final sun.misc.Unsafe U = sun.misc.Unsafe.getUnsafe();
private final long offset;
/**
* if field is protected, the subclass constructing updater, else
* the same as tclass
*/
private final Class<?> cclass;
/** class holding the field */
private final Class<T> tclass;
AtomicIntegerFieldUpdaterImpl(final Class<T> tclass,
final String fieldName,
final Class<?> caller) {
final Field field;
final int modifiers;
try {
field = AccessController.doPrivileged(
new PrivilegedExceptionAction<Field>() {
public Field run() throws NoSuchFieldException {
return tclass.getDeclaredField(fieldName);
}
});
modifiers = field.getModifiers();
sun.reflect.misc.ReflectUtil.ensureMemberAccess(
caller, tclass, null, modifiers);
ClassLoader cl = tclass.getClassLoader();
ClassLoader ccl = caller.getClassLoader();
if ((ccl != null) && (ccl != cl) &&
((cl == null) || !isAncestor(cl, ccl))) {
sun.reflect.misc.ReflectUtil.checkPackageAccess(tclass);
}
} catch (PrivilegedActionException pae) {
throw new RuntimeException(pae.getException());
} catch (Exception ex) {
throw new RuntimeException(ex);
}
if (field.getType() != int.class)
throw new IllegalArgumentException("Must be integer type");
if (!Modifier.isVolatile(modifiers))
throw new IllegalArgumentException("Must be volatile type");
// Access to protected field members is restricted to receivers only
// of the accessing class, or one of its subclasses, and the
// accessing class must in turn be a subclass (or package sibling)
// of the protected member's defining class.
// If the updater refers to a protected field of a declaring class
// outside the current package, the receiver argument will be
// narrowed to the type of the accessing class.
this.cclass = (Modifier.isProtected(modifiers) &&
tclass.isAssignableFrom(caller) &&
!isSamePackage(tclass, caller))
? caller : tclass;
this.tclass = tclass;
this.offset = U.objectFieldOffset(field);
}
...
}
它是如何操作一个对象中的某一个属性的?
使用了 反射 ,通过传入的Class和field名来得到成员变量,然后将其在对象中的位置存在offset中。
在构造Atomic对象时进行了一些条件的判读:
- 判断目标属性是否 对调用类可见 ,即AtomicIntegerFieldUpdater的使用类是否可以操作目标field。这里并没有通过反射设置属性的accessable,因为这就改变了原有类的可见性。
- 判断目标属性是否是 int基础类型
- 判断目标属性是否 有volatile 修饰,保证可见性
- 判断目标属性是否 不是static 的。为什么需要有这个条件限制呢,看构造函数的最后一行:this.offset = U.objectFieldOffset(field);使用了objectFieldOffset来获取成员变量的偏移量,而static修饰的变量有专门的获取偏移量方法:U.staticFieldOffset(field);AtomicIntegerFieldUpdater中并没有对这个地方做判断,所以限制了非static这一条件。
- 方法实现
与AtomicInteger等其他的原子类相似,如递增方法的实现:
public int getAndIncrement(T obj) {
int prev, next;
do {
prev = get(obj);
next = prev + 1;
} while (!compareAndSet(obj, prev, next));
return prev;
}
public final int get(T obj) {
accessCheck(obj);
return U.getIntVolatile(obj, offset);
}
总结——实现原理:反射+Volatile关键字+CAS