java(十五):concurrent(0)—AtomicInteger,Unsafe,CAS

来源:互联网 发布:Python Max iteritem 编辑:程序博客网 时间:2024/06/05 14:59

因为java在concurrent包中大量的使用到了AtomicInteger,于是打算从AtomicInteger开始对concurrent包的各个类做一番探究。
如果想对int型数据进行原子操作,那么推荐使用AtomicInteger。
当然也可以使用synchronized代码块,但使用AtomicInteger更高效。
如下代码说明了使用AtomicInteger和使用Integer的区别:

static AtomicInteger i = new AtomicInteger(0);public static void main(String[] args) {    Runnable[] threads = new Runnable[300];    for(int j=0;j<300;j++){        threads[j] = new Runnable() {            public void run() {                System.out.println(i.incrementAndGet());            }        };    }    for(int j=0;j<300;j++){        new Thread(threads[j]).start();    }}

上述的代码最后输出结果中最大为300.
再看下面的代码:

static Integer i = new Integer(0);public static void main(String[] args) {    Runnable[] threads = new Runnable[300];    for(int j=0;j<300;j++){        threads[j] = new Runnable() {            public void run() {                System.out.println(++i);            }        };    }    for(int j=0;j<300;j++){        new Thread(threads[j]).start();    }}

毫无疑问,将AtomicInteger换成Integer后,多线程没有进行同步,因此一般来说最后的结果中不会有300.
查看AtomicInteger的源码发现,其实同步多线程的操作是通过Unsafe对象来实现的。
于是来先来看看Unsafe吧。

Unsafe

Unsafe类提供了硬件级别的原子操作

对于不想细究的人来说,只需要记住一上一点就行了。即Unsafe提供了和操作系统、内存层面的操作,Unsafe类里面包含大量的native方法,其实就是将这些方法封装为Unsafe类,表示这些方法是底层实现的。

CAS

说道Unsafe,最著名的就是CAS操作,即Compare And Swap,比较并交换。
希望大家不要被这个词误导,CAS真的就是这么简单的意思,硬要在深挖一层的话那就是其实现的细节了。CAS使用Unsafe实现,具体来说是调用native方法,最常用的CAS方法如下:

public final class Unsafe {    //...    public final native boolean compareAndSwapInt(Object var1, long var2, int var4, int var5);

var1是比较值所属的对象,var2需要比较的值(但实际是使用地址偏移量来实现的),如果var1对象中偏移量为var2处的值等于var4,那么将该处的值设置为var5并返回true,如果不等于var4则返回false。
简单来说这就是CAS的全部了,作为使用者,记住这些就OK.

park

还包括unpark方法。

Unsafe提供了park方法和unpark方法用于挂起线程和恢复线程

park系列方法关系到锁的实现,具体到锁在讨论。

定位于修改

Unsafe还可以用于定位对象的属性的内存位置,修改属性值。

  • 定位属性位置
    对于一个类来说,该类的一个对象,其某个属性值相对于该对象的内存空间的位置是固定的,而类Unsafe可以帮我们获取到这个偏移量。
  • 定位属性值
    有了属性的偏移量,Unsafe还提供了根据对象,偏移量来获取该对象的值的方法。

上述两个功能的方法参考如下:

public final class Unsafe {    //获取类中属性的偏移量    public native long objectFieldOffset(Field var1);    //获取对象var1内部偏移量为var2的类型为Int的值    public native int getIntVolatile(Object var1, long var2);    //修改对象var1内部偏移量为var2的类型为Int的值为var4    public native void putIntVolatile(Object var1, long var2, int var4);

分配与释放内存

Unsafe甚至提供手动操作内存的操作,正如类名一样,这些操作都是不安全的,我们不应该去使用。
贴一份参考资料,讲unsafe的Java中Unsafe类详解

到这里我么简单浏览了一下Unsafe类,也知道了关于CAS的一些操作, 那么AtomicInteger是如何使用Unsafe来实现同步操作的呢?

AtomicInteger

方法实现

正如开头提到的,AtomicInteger正如类名,完成原子性操作,其大部分操作都是使用Unsafe类来实现的。

public class AtomicInteger extends Number implements java.io.Serializable {    //定义unsafe对象    private static final Unsafe unsafe = Unsafe.getUnsafe();    //表示value值的偏移量    private static final long valueOffset;    //静态方法获取value的偏移量并赋给valueOffset    static {        try {            valueOffset = unsafe.objectFieldOffset(AtomicInteger.class.getDeclaredField("value"));        } catch (Exception ex) { throw new Error(ex); }    }    private volatile int value;

注意AtomicInteger内部的代码,回顾java对象jvm(二):对象加载浅谈
,除了对象头等知识外,还有一点再此做补充:

一个类一旦定义完毕,其内部字段相对于对象的偏移量就固定了。

于是我们就可以使用类而不是对象来获取属性的偏移量了。
valueOffset = unsafe.objectFieldOffset(AtomicInteger.class.getDeclaredField("value"));

然后就调用Unsafe的方法了,如下:

public class AtomicInteger extends Number implements java.io.Serializable {    //...属性定义    //典型的CAS操作,如果value==expect,则设置value=update    public final boolean compareAndSet(int expect, int update) {        return unsafe.compareAndSwapInt(this, valueOffset, expect, update);    }    //设置value为newValue,并返回value    public final int getAndSet(int newValue) {        return unsafe.getAndSetInt(this, valueOffset, newValue);    }

AtomicInteger剩下的方法都是诸如自增,自减等操作了。

同步机制

说到底其并未使用常见的synchronized或者锁机制,那AtomicInteger的同步是如何实现的呢?
秘诀就在于CAS。
部分源码如下:

public class AtomicInteger extends Number implements java.io.Serializable {    //...    //设置value为newValue,并返回value    public final int getAndAdd(int delta) {        return unsafe.getAndAddInt(this, valueOffset, delta);    }    //...}public final class Unsafe {    //...    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;    }    //...}

假设有3个线程,同时调用对于同一个AtomicInteger的getAndAdd(1)操作,然后仔细分析上述代码,就会发现,即使中间的过程可能会存在差异,但最后该AtomicInteger一定是3.

参考JAVA 中无锁的线程安全整数 AtomicInteger介绍和使用,java中的CAS

阅读全文
0 0
原创粉丝点击