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)对于AtomicIntegerFieldUpdaterAtomicLongFieldUpdater 只能修改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接收的。不存在装箱过程。