java.util.concurrent.atomic中的类可以分成4组:
- 标量类(Scalar):AtomicBoolean,AtomicInteger,AtomicLong,AtomicReference
- 数组类:AtomicIntegerArray,AtomicLongArray,AtomicReferenceArray
- 更新器类:AtomicLongFieldUpdater,AtomicIntegerFieldUpdater,AtomicReferenceFieldUpdater
- 复合变量类:AtomicMarkableReference,AtomicStampedReference
第一组AtomicBoolean,AtomicInteger,AtomicLong,AtomicReference这四种基本类型用来处理布尔,整数,长整数,对象四种数据,其内部实现不是简单的使用synchronized,而是一个更为高效的方式CAS (compare and swap) + volatile和native方法,从而避免了synchronized的高开销,执行效率大为提升。如AtomicInteger的实现片断为:
private static final Unsafe unsafe = Unsafe.getUnsafe();
private volatile int value;
public final int get() {
return value;
}
public final void set(int newValue) {
value = newValue;
}
public final boolean compareAndSet(int expect, int update) {
return unsafe.compareAndSwapInt(this, valueOffset, expect, update);
}
复制代码
- 构造函数(两个构造函数)
- 默认的构造函数:初始化的数据分别是false,0,0,null
- 带参构造函数:参数为初始化的数据
- set( )和get( )方法:可以原子地设定和获取atomic的数据。类似于volatile,保证数据会在主存中设置或读取
- void set()和void lazySet():set设置为给定值,直接修改原始值;lazySet延时设置变量值,最终会设置成newValue,使用lazySet设置值后,可能导致其线程在之后的一小段时间内还是可以读到旧的值。
- getAndSet( )方法
- 原子的将变量设定为新数据,同时返回先前的旧数据
- 其本质是get( )操作,然后做set( )操作。尽管这2个操作都是atomic,但是他们合并在一起的时候,就不是atomic。在Java的源程序的级别上,如果不依赖synchronized的机制来完成这个工作,是不可能的。只有依靠native方法才可以。
public final int getAndSet(int newValue) {
for (;;) {
int current = get();
if (compareAndSet(current, newValue))
return current;
}
}
复制代码
compareAndSet( ) 和weakCompareAndSet( )方法
- 这 两个方法都是conditional modifier方法。这2个方法接受2个参数,一个是期望数据(expected),一个是新数据(new);如果atomic里面的数据和期望数据一 致,则将新数据设定给atomic的数据,返回true,表明成功;否则就不设定,并返回false。JSR规范中说:以原子方式读取和有条件地写入变量但 不 创建任何 happen-before 排序,因此不提供与除 weakCompareAndSet
public final boolean compareAndSet(int expect, int update) {
return unsafe.compareAndSwapInt(this, valueOffset, expect, update);
}
public final boolean weakCompareAndSet(int expect, int update) {
return unsafe.compareAndSwapInt(this, valueOffset, expect, update);
}
复制代码
- 对于 AtomicInteger、AtomicLong还提供了一些特别的方法。
getAndIncrement( ):以原子方式将当前值加 1,相当于线程安全的i++操作。
incrementAndGet( ):以原子方式将当前值加 1, 相当于线程安全的++i操作。
getAndDecrement( ):以原子方式将当前值减 1, 相当于线程安全的i--操作。
decrementAndGet ( ):以原子方式将当前值减 1,相当于线程安全的--i操作。
addAndGet( ): 以原子方式将给定值与当前值相加, 实际上就是等于线程安全的i =i+delta操作。
getAndAdd( ):以原子方式将给定值与当前值相加, 相当于线程安全的t=i;i+=delta;return t;操作。
以实现一些加法,减法原子操作。(注意 --i、++i不是原子操作,其中包含有3个操作步骤:第一步,读取i;第二步,加1或减1;第三步:写回内存)
通过getAndIncrement方法来看看实现原理:
public final int getAndIncrement() {
for (;;) {
int current = get();//先取得AtomicInteger存储的数组
int next = current + 1;//对当前数组加1
if (compareAndSet(current, next))//CAS操作更新,先检查当前数值是否等于current,如果是则将AtomicInteger的当前数组更新成next;如果不是,则返回false,重新循环更新
return current;//返回更新前的值
}
}
public final boolean compareAndSet(int expect, int update) {
return unsafe.compareAndSwapInt(this, valueOffset, expect, update);
}
复制代码
如何原子的更新其他的基本类型?
由于java.util.concurrent.atomic包的类都是使用Unsafe实现的,先看一下Unsafe的源码:
public native boolean compareAndSwapObject(Object obj, long offset,Object expect, Object update);
public native boolean compareAndSwapLong(Object obj, long offset,long expect, long update);
public native boolean compareAndSwapInt(Object obj, long offset,int expect, int update);
复制代码
通过代码,我们发现Unsafe只提供了3种CAS方法:compareAndSwapObject、compareAndSwapInt和compareAndSwapLong,再看AtomicBoolean源码,发现它是先把Boolean转换成整
型,再使用compareAndSwapInt进行CAS,所以原子更新char、float和double变量也可以用类似
的思路来实现。
// AtomicBoolean
public final boolean compareAndSet(boolean expect, boolean update) {
int e = expect ? 1 : 0;
int u = update ? 1 : 0;
return unsafe.compareAndSwapInt(this, valueOffset, e, u);
}
// AtomicDouble
public final boolean compareAndSet(double expect, double update) {
return updater.compareAndSet(this,Double.doubleToRawLongBits(expect),Double.doubleToRawLongBits(update));
}
复制代码
使用AtomicReference创建线程安全的堆栈
package thread;
import java.util.concurrent.atomic.AtomicReference;
public class ConcurrentStack<T> {
private AtomicReference<Node<T>> stacks = new AtomicReference<Node<T>>();
public T push(T e) {
Node<T> oldNode, newNode;
for (;;) { // 这里的处理非常的特别,也是必须如此的。
oldNode = stacks.get();
newNode = new Node<T>(e, oldNode);
if (stacks.compareAndSet(oldNode, newNode)) {
return e;
}
}
}
public T pop() {
Node<T> oldNode, newNode;
for (;;) {
oldNode = stacks.get();
newNode = oldNode.next;
if (stacks.compareAndSet(oldNode, newNode)) {
return oldNode.object;
}
}
}
private static final class Node<T> {
private T object;
private Node<T> next;
private Node(T object, Node<T> next) {
this.object = object;
this.next = next;
}
}
}
复制代码
虽然原子的标量类扩展了Number类,但并没有扩展一些基本类型的包装类,如Integer或Long,事实上他们也不能扩展:基本类型的包装类是不可以修改的,而原子变量类是可以修改的。在原子变量类中没有重新定义hashCode或equals方法,每个实例都是不同的,他们也不宜用做基于散列容器中的键值。
第二组AtomicIntegerArray,AtomicLongArray还有AtomicReferenceArray类进一步扩展了原子操作,对这些类型的数组提供了支持。这些类在为其数组元素提供
volatile
访问语义方面也引人注目,这对于普通数组来说是不受支持的。
他们内部并不是像AtomicInteger一样维持一个valatile变量,而是全部由native方法实现,如下
AtomicIntegerArray的实现片断:
private static final Unsafe unsafe = Unsafe.getUnsafe();
private static final int base = unsafe.arrayBaseOffset(int[].class);
private static final int scale = unsafe.arrayIndexScale(int[].class);
private final int[] array;
public final int get(int i) {
return unsafe.getIntVolatile(array, rawIndex(i));
}
public final void set(int i, int newValue) {
unsafe.putIntVolatile(array, rawIndex(i), newValue);
}
复制代码
上述几个类提供的方法几乎一样,我们以AtomicIntegerArray类为例讲解,其常用方法如下:
- int addAndGet(int i , int delta):以原子方式将输入值与数组中索引i的元素相加。
- boolean compareAndSet(int i , int expect , int update):如果当前值等于预期值,则以原子方式将数组位置i的元素设置成update值。
public class Test {
static int[] value = new int[]{1,2};
static AtomicIntegerArray ai = new AtomicIntegerArray(value);
public static void main(String[] args){
ai.getAndSet(0, 3);
System.out.println(ai.get(0));
System.out.println(value[0]);
}
}
复制代码
输出结果为:
3
1复制代码
第三组AtomicLongFieldUpdater,AtomicIntegerFieldUpdater,AtomicReferenceFieldUpdater基于反射的实用工具,可以对指定类的指定 volatile
字段进行原子更新。API非常简单,但是也是有一些约束:
(1)字段必须是volatile类型的
(2)字段的描述类型(修饰符public/protected/default/private)是与调用者与操作对象字段的关系一致。也就是说 调用者能够直接操作对象字段,那么就可以反射进行原子操作。但是对于父类的字段,子类是不能直接操作的,尽管子类可以访问父类的字段。
(3)只能是实例变量,不能是类变量,也就是说不能加static关键字。
(4)只能是可修改变量,不能使final变量,因为final的语义就是不可修改。实际上final的语义和volatile是有冲突的,这两个关键字不能同时存在。
(5)对于AtomicIntegerFieldUpdater和AtomicLongFieldUpdater 只能修改int/long类型的字段,不能修改其包装类型(Integer/Long)。如果要修改包装类型就需要使用AtomicReferenceFieldUpdater
原子更新类的字段,需要两步:
- 第一步:因为原子更新字段类都是抽象类,每次使用的时候必须使用静态方法newUpdater()创建一个更新器,并且需要设置想要更新的类和属性。
- 第二步:更新类的字段必须使用 public volatile修饰
AtomicIntegerFieldUpdater示例如下:
public class AtomicIntegerFieldUpdaterTest {
static class User{
private String name;
private int old;
public User(String name,int old){
this.name=name;
this.old=old;
}
public String getName() {
return name;
}
public int getOld() {
return old;
}
}
private static AtomicIntegerFieldUpdater<User> aifu = AtomicIntegerFieldUpdater.newUpdater(User.class,"old");
public static void main(String[] args){
User user = new User("Tom", 15);
System.out.println(aifu.getAndIncrement(user));//old加1,但是仍然会输出15
System.out.println(aifu.get(user));//16
}
}
复制代码
输入结果如下:
15
16
示例2:
package com.lipeng.seventh;
import java.util.concurrent.atomic.AtomicReferenceFieldUpdater;
/**
* AtomicReferenceFieldUpdaterDemo
* 原子更新User类中userName字段,此字段必须定义为public且volatile
* @author LiPeng
*
*/
public class AtomicReferenceFieldUpdaterDemo {
static AtomicReferenceFieldUpdater<User, String> atomicFiled=AtomicReferenceFieldUpdater.newUpdater(User.class, String.class, "userName");
public static void main(String[] args) {
User user=new User("1","zhangsan");
atomicFiled.compareAndSet(user, user.userName, "lisi");
System.out.println(atomicFiled.get(user));
}
static class User{
public String userId;
public volatile String userName;
public String getUserId() {
return userId;
}
public void setUserId(String userId) {
this.userId = userId;
}
public String getUserName() {
return userName;
}
public void setUserName(String userName) {
this.userName = userName;
}
public User(String userId, String userName) {
super();
this.userId = userId;
this.userName = userName;
}
@Override
public String toString() {
return "User [userId=" + userId + ", userName=" + userName + "]";
}
}
}
1
复制代码
AtomicIntegerFieldUpdater来实现的,看下面代码:
//定义
private static final AtomicLongFieldUpdater<ChannelOutboundBuffer> TOTAL_PENDING_SIZE_UPDATER =
AtomicLongFieldUpdater.newUpdater(ChannelOutboundBuffer.class, "totalPendingSize");
private volatile long totalPendingSize;
//使用
long oldValue = totalPendingSize;
long newWriteBufferSize = oldValue + size;
while (!TOTAL_PENDING_SIZE_UPDATER.compareAndSet(this, oldValue, newWriteBufferSize)) {
oldValue = totalPendingSize;
newWriteBufferSize = oldValue + size;
}
复制代码
第四组 AtomicMarkableReference,AtomicStampedReference 用于解决ABA问题
CAS操作可能存在ABA的问题,就是说:
假如一个值原来是A,变成了B,又变成了A,那么CAS检查时会发现它的值没有发生变化,但是实际上却变化了。
AtomicStampedReference原理
先来看下如何构造一个AtomicStampedReference对象,AtomicStampedReference只有一个构造器:
public class AtomicStampedReference<V> {
private static class Pair<T> {
final T reference;
final int stamp; // int 值
private Pair(T reference, int stamp) {
this.reference = reference;
this.stamp = stamp;
}
static <T> Pair<T> of(T reference, int stamp) {
return new Pair<T>(reference, stamp);
}
}
private volatile Pair<V> pair;
/**
* Creates a new {@code AtomicStampedReference} with the given
* initial values.
*
* @param initialRef the initial reference
* @param initialStamp the initial stamp
*/
public AtomicStampedReference(V initialRef, int initialStamp) {
pair = Pair.of(initialRef, initialStamp);
}复制代码
可以看到,除了传入一个初始的引用变量initialRef外,还有一个initialStamp变量,initialStamp其实就是版本号(或者说时间戳),用来唯一标识引用变量。
在构造器内部,实例化了一个Pair对象,Pair对象记录了对象引用和时间戳信息,采用int作为时间戳,实际使用的时候,要保证时间戳唯一(一般做成自增的),如果时间戳如果重复,还会出现ABA的问题。
AtomicStampedReference的所有方法,其实就是Unsafe类针对这个Pair对象的操作。
和AtomicReference相比,AtomicStampedReference中的每个引用变量都带上了pair.stamp这个版本号,这样就可以解决CAS中的ABA问题了。
package com.vatuse.thread;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.concurrent.atomic.AtomicStampedReference;
public class ABATest {
private static AtomicInteger atomicInt = new AtomicInteger(100);
private static AtomicStampedReference<Integer> atomicStampedRef = new AtomicStampedReference<Integer>(100, 0);
public static void main(String[] args) throws InterruptedException {
Thread intT1 = new Thread(new Runnable() {
@Override
public void run() {
atomicInt.compareAndSet(100, 101);
atomicInt.compareAndSet(101, 100);
}
});
Thread intT2 = new Thread(new Runnable() {
@Override
public void run() {
try {
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
}
boolean c3 = atomicInt.compareAndSet(100, 101);
System.out.println(c3); // true
}
});
intT1.start();
intT2.start();
intT1.join();
intT2.join();
Thread refT1 = new Thread(new Runnable() {
@Override
public void run(){
try {
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
}
//100-->101
atomicStampedRef.compareAndSet(100, 101, atomicStampedRef.getStamp(), atomicStampedRef.getStamp() + 1);
// 101-->100
atomicStampedRef.compareAndSet(101, 100, atomicStampedRef.getStamp(), atomicStampedRef.getStamp() + 1);
}
});
Thread refT2 = new Thread(new Runnable() {
@Override
public void run() {
int stamp = atomicStampedRef.getStamp();
try {
TimeUnit.SECONDS.sleep(2);
} catch (InterruptedException e) {
}
//100-->101 对比版本号,虽然值一样,但是认为不同,cas失败
boolean c3 = atomicStampedRef.compareAndSet(100, 101, stamp, stamp + 1);
System.out.println(c3); // false
}
});
refT1.start();
refT2.start();
}
}
复制代码
我们在讲ABA问题的时候,引入了AtomicStampedReference。
AtomicStampedReference可以给引用加上版本号,追踪引用的整个变化过程,如:
A -> B -> C -> D - > A,通过AtomicStampedReference,我们可以知道,引用变量中途被更改了3次
但是,有时候,我们并不关心引用变量更改了几次,只是单纯的关心是否更改过,所以就有了AtomicMarkableReference:
public class AtomicMarkableReference<V> {
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);
}
}
private volatile Pair<V> pair;
/**
* Creates a new {@code AtomicMarkableReference} with the given
* initial values.
*
* @param initialRef the initial reference
* @param initialMark the initial mark
*/
public AtomicMarkableReference(V initialRef, boolean initialMark) {
pair = Pair.of(initialRef, initialMark);
}复制代码
可以看到,AtomicMarkableReference的唯一区别就是Pair类不再用int标识引用,而是使用boolean变量——表示引用变量是否被更改过。
从语义上讲,AtomicMarkableReference对于那些不关心引用变化过程,只关心引用变量是否变化过的应用会更加友好。
(个人觉得这个类比较鸡肋,不能完全解决ABA问题)
一个坑
该demo企图将integer的值从0一直增长到1000,但当integer的值增长到128后,将停止增长。出现该现象有两点原因:1、使用int类型而非Integer保存当前值,2、Interger对-128~127的缓存
查看compareAndSet方法。expectedReference为引用类型V,此处为Integer。
当int类型的current传入compareAndSet方法时,会被装箱成Integer类型与expectedReference进行==比较。
装箱过程是调用Integer的valueOf方法。看看Integer的valueOf方法。
Integer对-128~127间的数字有缓存,所以在此区间的current==expectedReference为true,当current>127,每次装箱将返回新的Integer对象,此时current!=expectedReference导致compareAndSet一直返回false。 改用Integer接收current后,问题解决,因为current与expectedReference始终为同一Integer对象。 当然也可以不用中间变量存储当前值,直接如图,不会有问题。
timeStamp为什么不存在这个问题,因为compareAndSet方法中使用int接收的。不存在装箱过程。