解决并发的线程安全问题有两种方式:
1、等待唤醒机制
如果抢不到锁,就将线程挂起,当锁释放的时候,然后将其唤醒重新抢锁。
2、自旋CAS
自旋就是设置循环CAS抢锁的意思,当CAS成功的时候才会退出循环
名称 | 适用场景 |
---|---|
等待唤醒机制 | 当长时间都无法抢到锁的时候,还是将线程挂起,然后等待唤醒的好。因为等待和唤醒牵扯到线程挂起和切换,会导致从用户态到内核态的切换,并且线程切换会导致上下文的切换,现场保存什么的,会比较浪费资源 |
自旋CAS | 当短时间内就可以获取到锁的时候,自旋CAS比较合适,短时间的自旋CAS肯定会比线程切换消耗的资源要少,如果要是时间长的话,就不太划算了,因为自旋CAS会一直占用CPU |
Atomic原子类就是利用自旋CAS来保证线程安全的。
public class TestAtomic {
int age = 0;
Lock lock = new ReentrantLock();
AtomicInteger ageAtic = new AtomicInteger(0);
//加锁方式
public void setAgeByLock(){
lock.lock();
age++;
lock.unlock();
}
//原子类方式
public void setAgeByAtomic(){
ageAtic.getAndIncrement();
}
}
拿例子中的ageAtic.getAndIncrement()来看其源码
public final int getAndIncrement() {
//this对应着当前对应
//valueOffset对应着当前属性在对象中的内存偏移地址
//1代表着增加的数量
return unsafe.getAndAddInt(this, valueOffset, 1);
}
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));//自旋CAS,var5是希望的旧值,var5+var4是新值,只有当内存中的值等于var5,也就是当前内存中的值等于希望的旧值的时候,才会更新成功,返回true
return var5;
}
1、基本类型
AtomicInteger、AtomicBoolean、AtomicLong
方法如下所示,都很简单,感兴趣的可以去试一试
getAndIncrement() // 原子化 i++
getAndDecrement() // 原子化的 i--
incrementAndGet() // 原子化的 ++i
decrementAndGet() // 原子化的 --i
// 当前值 +=delta,返回 += 前的值
getAndAdd(delta)
// 当前值 +=delta,返回 += 后的值
addAndGet(delta)
//CAS 操作,返回是否成功
compareAndSet(expect, update)
// 以下四个方法
// 新值可以通过传入 func 函数来计算
getAndUpdate(func)
updateAndGet(func)
getAndAccumulate(x,func)
accumulateAndGet(x,func)
2、对象引用类型
AtomicReference
AtomicReference利用CAS来更新引用,旧值为原来的引用对象,新值为新的引用对象。 是更新引用,而不是更新对象。
看下面的源码就知道了,其实传入的对象就是AtomicReference的一个属性名字叫value的取值,通过CAS来更改value的取值,通过AtomicReference.get()来获取最新的值。
User user = new User("jaychou",24);
AtomicReference<User> userAtomicReference = new AtomicReference<>(user);
while (true){
User updateUser = new User("jay",22);
boolean flag = userAtomicReference.compareAndSet(userAtomicReference.get(),updateUser);
if (flag){
System.out.println(userAtomicReference.get());
break;
}
}
源码
public AtomicReference(V initialValue) {
value = initialValue;
}
static {
try {
valueOffset = unsafe.objectFieldOffset
(AtomicReference.class.getDeclaredField("value"));
} catch (Exception ex) { throw new Error(ex); }
}
public final boolean compareAndSet(V expect, V update) {
return unsafe.compareAndSwapObject(this, valueOffset, expect, update);
}
看源码就知道了,对象的compareAndSet可能会返回false,所以在使用的时候往往需要搭配自旋来更新。
AtomicStampedReference
AtomicReference有一个缺点,不能解决ABA问题。
ABA问题就是多个线程前后修改值,导致线程CAS前后值没有变化,但是中间却发生了修改。
AtomicStampedReference通过引入时间戳来解决了ABA问题。每次要更新值的时候,需要额外传入oldStamp和newStamp。将对象和stamp包装成了一个Pair对象。
User user = new User("jaychou",24);
AtomicStampedReference<User> userAtomicStampedReference = new AtomicStampedReference<>(user,1);
while (true){
User user1 = new User("jay",222);
int oldStamp1 = userAtomicStampedReference.getStamp();
int[] stamp = new int[1];
User oldUser = userAtomicStampedReference.get(stamp);
boolean flag = userAtomicStampedReference.compareAndSet(oldUser,user1,stamp[0],stamp[0]+1);
if (flag){
break;
}
}
int[] s = new int[1];
System.out.println(userAtomicStampedReference.get(s));
System.out.println(s[0]);
源码
private static class Pair<T> {
//对象
final T reference;
//时间戳
final int stamp;
private Pair(T reference, int stamp) {
this.reference = reference;
this.stamp = stamp;
}
//通过reference和stamp包装成Pair然后返回
static <T> Pair<T> of(T reference, int stamp) {
return new Pair<T>(reference, stamp);
}
}
public AtomicStampedReference(V initialRef, int initialStamp) {
//初始化pair
pair = Pair.of(initialRef, initialStamp);
}
//CAS更新
public boolean compareAndSet(V expectedReference,
V newReference,
int expectedStamp,
int newStamp) {
//获取当前的pair
Pair<V> current = pair;
return
//传入的旧reference和当前current的reference相等
//传入的旧stamp和当前current的stamp相等
expectedReference == current.reference &&
expectedStamp == current.stamp &&
//current的reference和newReference相等,current的stamp和newStamp相等,说明修改的值和当前值一样,直接返回true。不会再CAS了
((newReference == current.reference &&
newStamp == current.stamp) ||
//通过Pair.of(newReference,newStamp)来新建Pair,然后利用CAS来修改
casPair(current, Pair.of(newReference, newStamp)));
}
//获取reference和stamp
public V get(int[] stampHolder) {
Pair<V> pair = this.pair;
//stamp存储在传入的stampHolder[0]
stampHolder[0] = pair.stamp;
//
return pair.reference;
}
AtomicMarkableReference
private static class Pair<T> {
final T reference;
final boolean mark;
private Pair(T reference, boolean mark) {
this.reference = reference;
this.mark = mark;
}
static <T> Pair<T> of(T reference, boolean mark) {
return new Pair<T>(reference, mark);
}
}
public AtomicMarkableReference(V initialRef, boolean initialMark) {
pair = Pair.of(initialRef, initialMark);
}
public boolean compareAndSet(V expectedReference,
V newReference,
boolean expectedMark,
boolean newMark) {
Pair<V> current = pair;
return
expectedReference == current.reference &&
expectedMark == current.mark &&
((newReference == current.reference &&
newMark == current.mark) ||
casPair(current, Pair.of(newReference, newMark)));
}
public V get(boolean[] markHolder) {
Pair<V> pair = this.pair;
markHolder[0] = pair.mark;
return pair.reference;
}
从上述代码看,AtomicMarkableReference跟AtomicStampedReference的结构类似,只不过AtomicMarkableReference中包装了一个boolean变量,而AtomicStampedReference包装类一个int变量。
AtomicMarkableReference是无法解决ABA问题的,因为boolean变量的mark是有很大可能重合的,还是会导致更新成功。
暂时没搞明白AtomicMarkableReference有什么应用场景!网上关于AtomicMarkableReference的资料也很少
Atomic数组
Atomic数组主要有AtomicIntegerArray、AtomicLongArray、AtomicReferenceArray.
例子
AtomicIntegerArray integerArray = new AtomicIntegerArray(10);//10为数组长度
while (true){
boolean flag = integerArray.compareAndSet(0,integerArray.get(0),2);
if (flag){
System.out.println(integerArray.get(0)+"---"+flag);
break;
}
}
AtomicReferenceArray<User> referenceArray = new AtomicReferenceArray<>(10);
while (true){
boolean flag2 = referenceArray.compareAndSet(0,referenceArray.get(0),new User("jaychou",22));
if (flag2){
System.out.println(referenceArray.get(0)+"---"+flag2);
break;
}
}
源码
public AtomicReferenceArray(int length) {
array = new Object[length];
}
public final E get(int i) {
return getRaw(checkedByteOffset(i));
}
private long checkedByteOffset(int i) {
if (i < 0 || i >= array.length)
throw new IndexOutOfBoundsException("index " + i);
return byteOffset(i);
}
//unsafe = Unsafe.getUnsafe();
//arrayFieldOffset = unsafe.objectFieldOffset
// (AtomicReferenceArray.class.getDeclaredField("array"));
//base = unsafe.arrayBaseOffset(Object[].class);
//int scale = unsafe.arrayIndexScale(Object[].class);
//if ((scale & (scale - 1)) != 0)
// throw new Error("data type scale not a power of two");
//shift = 31 - Integer.numberOfLeadingZeros(scale);
private static long byteOffset(int i) {
return ((long) i << shift) + base;
}
//通过offset来获取内存中的值
private E getRaw(long offset) {
return (E) unsafe.getObjectVolatile(array, offset);
}
//通过
public final boolean compareAndSet(int i, E expect, E update) {
return compareAndSetRaw(checkedByteOffset(i), expect, update);
}
private boolean compareAndSetRaw(long offset, E expect, E update) {
return unsafe.compareAndSwapObject(array, offset, expect, update);
}
对象属性原子更新器
有三类:AtomicIntegerFieldUpdater(修改对象中的)、AtomicLongFieldUpdater、AtomicReferenceFieldUpdater
User user = new User("jaychou",22);
//初始化参数分别为:对应对应的类,属性对应的类,属性的名字
AtomicReferenceFieldUpdater fieldUpdater = AtomicReferenceFieldUpdater.newUpdater(User.class,String.class,"name");
//初始化参数分别为:对应对应的类,属性的名字。属性对应的类可以忽略,因为类名中已经记录了
AtomicIntegerFieldUpdater integerFieldUpdater = AtomicIntegerFieldUpdater.newUpdater(User.class,"age");
fieldUpdater.compareAndSet(user,user.name,"666");
integerFieldUpdater.compareAndSet(user,user.age,1000);
System.out.println(user);
class User{
int age;
volatile String name;
public User(String name,int age){
this.name = name;
this.age = age;
}
@Override
public String toString() {
return "User{" +
"age=" + age +
", name='" + name + '\'' +
'}';
}
}
从上述例子可以看出,AtomicReferenceFieldUpdater是对象实例之间可以公用的,compareAndSet方法是需要传入对象实例的。
源码分析:
public static <U,W> AtomicReferenceFieldUpdater<U,W> newUpdater(Class<U> tclass,Class<W> vclass,String fieldName) { //构建一个AtomicReferenceFieldUpdaterImpl对象
return new AtomicReferenceFieldUpdaterImpl<U,W>
(tclass, vclass, fieldName, Reflection.getCallerClass());
}
AtomicReferenceFieldUpdaterImpl(final Class<T> tclass,
final Class<V> vclass,
final String fieldName,
final Class<?> caller) {
final Field field;
final Class<?> fieldClass;
final int modifiers;
try {
//通过反射来获取fieldName对应的Field
field = AccessController.doPrivileged(
new PrivilegedExceptionAction<Field>() {
public Field run() throws NoSuchFieldException {
return tclass.getDeclaredField(fieldName);
}
});
//获取Field的访问权限
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);
}
//Field对应的class
fieldClass = field.getType();
} catch (PrivilegedActionException pae) {
throw new RuntimeException(pae.getException());
} catch (Exception ex) {
throw new RuntimeException(ex);
}
this.cclass = (Modifier.isProtected(modifiers) &&
tclass.isAssignableFrom(caller) &&
!isSamePackage(tclass, caller))
? caller : tclass;
this.tclass = tclass;
this.vclass = vclass;
//获取field在对象中的偏移量,用于CAS
this.offset = U.objectFieldOffset(field);
public final boolean compareAndSet(T obj, V expect, V update) {
accessCheck(obj);
valueCheck(update);
//通过obj和offset来确定Field对应的内存地址,然后CAS更新
return U.compareAndSwapObject(obj, offset, expect, update);
}
Java中提供了几类原子操作类,通过自旋+CAS来解决线程不安全问题。
1、AtomicLong、AtomicInteger、AtomicBoolean
这些类实现了++,–,+delta的原子操作。
2、AtomicReference
AtomicReference的作用是引用类型的原子修改,保证引用的修改不会出现线程问题。
并且通过AtomicStampedReference解决了ABA问题。
3、AtomicIntegerArray、AtomicLongArray、AtomicReferenceArray
这个就是数组类型,和单独的对象操作基本一致,只不过在设置的时候需要填入下标罢了。
4、AtomicIntegerFieldUpdater、AtomicReferenceFieldUpdater、AtomicLongFieldUpdater
AtomicReference是修改引用,而AtomicReferenceFieldUpdater这三个类是用来修改实例对象中的属性的值的。
AtomicIntegerFieldUpdater、AtomicLongFieldUpdater是AtomicReferenceFieldUpdater的一个特殊例子,是用来专门分别修改int和long属性的变量的。
被修改变量必须用volatile修饰