Java-原子操作和原子变量

API链接:https://docs.oracle.com/javase/8/docs/api/

所在包:java.util.concurrent.atomic

 

原子操作:不可被中断的一个或一系列操作。在多核处理器上实现原子操作会变得复杂许多。

Java8种数据类型,并且每个数据类型都有一个包装类,如intInteger,它们之间的转化也就是我们常称作的自动拆箱和装箱的过程。但是它们只是一个简单的数据,当在并发编程下,没有任何特殊的语义。

volatile能保证可见性,以及阻止编译器和处理器对其重排序,并且对单一数据读写具有原子性,然而对于复合操作却没有原子性,比如i++
那么如果需要一种原子性int那就是atomic包下面的AtomicInteger了。

AtomicXXX具体的实现:volatile的基本数据类型+CAS操作。
volatile
保证可见性,当一个线程修改volatile变量时,其他线程拿到的都是修改后的最新值。

里面的方法都是对Unsafe方法的封装,而Unsafe里面的方法都是JNI方法,通过调用底层c++方法从而实现指令级的原子操作。

 

一、原子更新基本类型

使用原子方式更新基本类型,共包括3个类:

扫描二维码关注公众号,回复: 2407198 查看本文章

AtomicBoolean:原子更新布尔变量

AtomicInteger:原子更新整型变量

AtomicLong:原子更新长整型变量

 

AtomicInteger为例解释方法的用处:

构造方法:

public AtomicInteger(int initialValue)

public AtomicInteger()初始值为0

 

 get set方法:

public final int get()

public final void set(int newValue)

public final void lazySet(int newValue){
       unsafe.putOrderedInt(this,valueOffset, newValue);
} //
没有storeload屏障的set1.6之后才有

public final int getAndSet(int newValue){
        return unsafe.getAndSetInt(this,valueOffset,newValue);
} //
获取当前值并set新值

public final boolean compareAndSet(int expect,int update) {
        return unsafe.compareAndSwapInt(this,valueOffset, expect, update);
}//
比较新值与期望相符则set新值

public final boolean weakCompareAndSet(int expect,int update)  {
        return unsafe.compareAndSwapInt(this,valueOffset, expect, update);
} //weak
CAS,也就是没有volatile语义的CAS,没有加入内存屏障


加减操作:

public final int getAndIncrement() {
        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));

       return var5;

    }

 

public final int getAndDecrement() {
        return unsafe.getAndAddInt(this,valueOffset, -1);
} //
获取当前值并减一

public final int getAndAdd(int delta) {
        return unsafe.getAndAddInt(this,valueOffset, delta);
} //
获取当前值并加上差值

public final int incrementAndGet() {
        return unsafe.getAndAddInt(this,valueOffset, 1) + 1;
}//
加一之后获取当前值

public final int decrementAndGet(){ 

       return unsafe.getAndAddInt(this, valueOffset, -1) - 1; 

    }  //减一之后获取当前值

public final int addAndGet(int delta) { 

       return unsafe.getAndAddInt(this, valueOffset, delta) + delta; 

    }  //加差值之后获取当前值

 

 阻塞式更新,并且对prev进行一个IntUnaryOperator操作运算:

public final int getAndUpdate(IntUnaryOperator updateFunction){ 

       int prev, next; 

       do { 

           prev =get(); 

           next =updateFunction.applyAsInt(prev); 

       } while(!compareAndSet(prev, next)); 

       return prev; 

    } 

public final int updateAndGet(IntUnaryOperator updateFunction) {
        int prev, next;
        do{            prev = get();
            next=updateFunction.applyAsInt(prev);
        } while (!compareAndSet(prev,next));
        return next;
}

阻塞式更新,并对prevx,进行二元运算操作:

public final int getAndAccumulate(int x,IntBinaryOperator accumulatorFunction) {
        int prev, next;
        do{            prev =get();
            next=accumulatorFunction.applyAsInt(prev, x);
        } while (!compareAndSet(prev,next));
        return prev;
    }

public final int accumulateAndGet(int x,IntBinaryOperator accumulatorFunction)(intx, 

                                     IntBinaryOperatoraccumulatorFunction) { 

       int prev, next; 

       do { 

           prev =get(); 

           next =accumulatorFunction.applyAsInt(prev, x); 

       } while(!compareAndSet(prev, next)); 

       return next; 

    } 

 都是通过一个死循环进行反复的进行CAS操作,直到更新成功才返回


转换成基础值:

public int intValue(){  

    return (long)get();  

}  

public long longValue()

public float floatValue()

public double doubleValue()

 

lazySet

从上面代码可以看到,由于valuevolatile类型,所以普通方法set,就是写入volatile类型变量。此时JVM会插入特定的内存屏障,内存语义具有可见性。

lazySet懒惰setset是内存语义是立即对其他线程可见,则lazySet则是不一定立即可见。

1.首先set()是对volatile变量的一个写操作,我们知道volatilewrite为了保证对其他线程的可见性会追加以下两个Fence(内存屏障)

1)StoreStore // intel cpu , 不存在 [ 写写 ] 重排序 , 这个可以直接省略了
2)StoreLoad //
这个是所有内存屏障里最耗性能的
2.   lazySet() 省去了 StoreLoad 屏障 , 只留下 StoreStore 。详见 Doug Lea 的描述:
所以这样一来,在效率上毫无疑问 lazySet 要比 set 高很多,可以这样理解, lazySet intel cpu 中,其实就可以看做普通变量的写操作了。
lazySet set() 具有性能优势,但是使用场景很有限。

相关参考:http://ifeve.com/juc-atomic-class-lazyset-que/

weakCompareAndSet

基于Java8分析,在上述代码中,可以很明显的看到weakCompareAndSet方法和compareAndSet方法,具有相同的实现,但是为啥名字不同呢?
其实按照DougLea本来的设计意图,是想吧weakCompareAndSet设定为一种在性能上更加高效的方法。
由于compareAndSet和其他读取并更新的操作,拥有相同的内存语义,即具有原子性。
所以设计了weakCompareAndSet方法,在读上具有通用的原子性,但是写方面不具有volatile语义了,换而言之,weakCompareAndSet的写操作,不能即时被其他线程可见。
上述详情参见:http://bugs.java.com/bugdatabase/view_bug.do?bug_id=4996165

IntUnaryOperatorIntBinaryOperator

在上面代码最下面,可以看到两个具有阻塞性的方法,updateAndGetgetAndAccumulate,这两个方法是Java8新加入的,增加了函数式编程的运用。
IntUnaryOperator

@Functional Interface

Public interface IntUnaryOperator{

/**

*一个操作数的函数

*/

    int applyAsInt(int operand);

    //compose

    defalut IntUnaryOperator compose(IntUnaryOperator before){

        Objects requireNonNull(before);

        return (int v)->applyAsInt(before.applyAsInt(v));

    }

    //andThen

    default IntUnaryOperator andThen(IntUnaryOperator after){

        Objects requireNonNull(after);

        return (int i)->after.applyAsInt(t);

    }

    //返回当前运算值

    static IntUnaryOperator identity(){

        return t->t;

    }

}

如上,IntUnaryOperator就是一个队单个int型数的操作运算,而composeandThen,可以参看:Java8Function接口学习(composeandThen)

IntBinaryOperator
IntBinaryOperator
则更加简单,也是函数式的方法接口,就只有一个待实现方法:

@Functional Interface

Public interface IntBinaryOperator{

   /**

    * 两个int的原酸

    *

    *@param left the firstoperand

    *@param right thesecond operand

    *@return the operatorresult

    */

    int applyAsInt(intleft, int right);

}

 

AtomicLong注意点:

有些机器是32位的,所以会把64long类型volatile拆分成232位进行计算,但有些不是的。所以在实现AtomicLong的时候,如果是32位,那就需要加上锁来实现CAS操作。JVM特别的还加了一个判断,来区分是32位:

/**
     * Records whether the underlying JVM supports lockless
     * compareAndSwap for longs. While the Unsafe.compareAndSwapLong
     * method works in either case, some constructions shouldbe
     * handled at Java level to avoid locking user-visible locks.
     */
   
static final boolean VM_SUPPORTS_LONG_CAS =VMSupportsCS8();

    /**
     * Returns whether underlying JVM supports lockless CompareAndSet
     * for longs. Called only once and cachedinVM_SUPPORTS_LONG_CAS.
     */
    private static native Boolean VMSupportsCS8();

AtomicBoolean注意点:

AtomicBoolean中,作为volatile变量的,并不是boolean类型,而是一个int类型的01来分别表示falsetrue。

Private volatile int value;

/**

*Creates a new {@code AtomicBoolean} with the given initial value.

*@param initialValuethe initial value

*/

publicAtomicBoolean(boolean initialValue) {

    value= initialValue ?1:0;

}


二、原子更新数组

注意,Java中Atomic*Array,并不是对整个数组对象实现原子化(也没有必要这样做),而是对数组中的某个元素实现原子化。


通过原子更新数组里的某个元素,共有3个类:

AtomicIntegerArray:原子更新整型数组的某个元素

AtomicLongArray:原子更新长整型数组的某个元素

AtomicReferenceArray:原子更新引用类型数组的某个元素

 

AtomicIntegerArray常用的方法有:

int addAndSet(int i, int delta):以原子方式将输入值与数组中索引为i的元素相加

boolean compareAndSet(int i,int expect,int update):如果当前值等于预期值,则以原子方式更新数组中索引为i的值为update

AtomicIntegerArray会将当前数组拷贝一份,所以在数组拷贝的操作不影响原数组的值。

   

    package com.rhwayfun.concurrency.r0405;
   
import java.util.concurrent.atomic.AtomicIntegerArray;
   
/**
     * Created by rhwayfun on 16-4-5.
     */

   
public classAtomicIntegerArrayDemo {

        staticint[] value =newint[]{1,2};
       
static AtomicIntegerArray ai =new AtomicIntegerArray(value);
       
public staticvoidmain(String[] args){
            ai.getAndSet(
0,3);
            System.out.println(ai.get(
0));
            System.out.println(value[
0]);
        }
    }

运行结果:

3

1


三、原子更新引用类型

引用的操作本身不就是原子的吗?

一个对象的引用,从A切换到B,本身也不会出现 非原子操作啊?这种想法本身没有什么问题,

但是考虑下嘛的场景:对象a,当前执行引用a1,

线程X期望将a的引用设置为a2,也就是a=a2,

线程Y期望将a的引用设置为a3,也就是a=a3。

X要求,a必须从a1变为a2,也就是说compareAndSet(expect=a1,setValue=a2);

Y要求,a必须从a1变为a3,也就是说compareAndSet(expect=a1,setValue=a3)。

如果严格遵循要求,应该出现X把a的引用设置为a2后,Y线程操作失败的情况,也就是说:

X:a==a1--> a=a2;

Y:a!=a1 --> Exception;

如果没有原子化,那么Y会直接将a赋值为a3,从而导致出现脏数据。

这就是原子引用AtomicReference存在的原因。


需要更新引用类型往往涉及多个变量,早atomic包有三个类:

AtomicReference:原子更新引用类型

AtomicReferenceFieldUpdater:原子更新引用类型里的字段

AtomicMarkableReference:原子更新带有标记位的引用类型。


public final V getAndSet(V newValue) {
2           while (true) {
3                V x = get();
4               if (compareAndSet(x, newValue))
5                   return x;
6           }
7        }

注意,AtomicReference要求引用也是volatile的。

 下面以AtomicReference为例进行说明:

package com.rhwayfun.concurrency.r0405;

import java.util.concurrent.atomic.AtomicReference;

/**
 * Created by rhwayfun on 16-4-5.
 */
public class AtomicReferenceDemo {

    static class User{
        private String name;
        private int id;

        public User(String name, int id) {
            this.name = name;
            this.id = id;
        }

        public String getName() {
            return name;
        }

        public void setName(String name) {
            this.name = name;
        }

        public int getId() {
            return id;
        }

        public void setId(int id) {
            this.id = id;
        }
    }

    public static AtomicReference<User> ar = new AtomicReference<User>();

    public static void main(String[] args){
        User user = new User("aa",11);
        ar.set(user);
        User newUser = new User("bb",22);
        ar.compareAndSet(user,newUser);
        System.out.println(ar.get().getName());
        System.out.println(ar.get().getId());
    }
}
运行结果为:bb 22可以看到user被成功更新。


四、原子更新字段类

其它几个Atomic类,都是对被volatile修饰的基本数据类型的自身数据进行原子化操作,

但是如果一个被volatile修饰的变量本身已经存在在类中,那要如何提供原子化操作呢?


如果需要原子更新某个类的某个字段,就需要用到原子更新字段类,可以使用以下几个类:

AtomicIntegerFieldUpdater:原子更新整型字段

AtomicLongFieldUpdater:原子更新长整型字段

AtomicStampedReference:原子更新带有版本号的引用类型。

要想原子更新字段,需要两个步骤:

1.     每次必须使用newUpdater创建一个更新器,并且需要设置想要更新的类的字段

2.     更新类的字段(属性)必须为public volatile

 下面的代码演示如何使用原子更新字段类更新字段:

package com.rhwayfun.concurrency.r0405;

import java.util.concurrent.atomic.AtomicIntegerFieldUpdater;

/**
 * Created by rhwayfun on 16-4-5.
 */
public class AtomicIntegerFieldUpdaterDemo {

    //创建一个原子更新器
    private static AtomicIntegerFieldUpdater<User> atomicIntegerFieldUpdater =
            AtomicIntegerFieldUpdater.newUpdater(User.class,"old");

    public static void main(String[] args){
        User user = new User("Tom",15);
        //原来的年龄
        System.out.println(atomicIntegerFieldUpdater.getAndIncrement(user));
        //现在的年龄
        System.out.println(atomicIntegerFieldUpdater.get(user));
    }

    static class User{
        private String name;
        public volatile int old;

        public User(String name, int old) {
            this.name = name;
            this.old = old;
        }

        public String getName() {
            return name;
        }

        public void setName(String name) {
            this.name = name;
        }

        public int getOld() {
            return old;
        }

        public void setOld(int old) {
            this.old = old;
        }
    }
}

输出的结果如下:

15
16


参考: 

http://blog.csdn.net/u011116672/article/details/51068828

http://blog.csdn.net/anla_/article/details/78652509

https://www.cnblogs.com/my376908915/p/6758415.html

https://www.jianshu.com/p/9ff426a784ad


猜你喜欢

转载自blog.csdn.net/u010898743/article/details/79449420
今日推荐