JDk8 AtomicInteger源码分析 CAS

  1. AtomicInteger的使用场景
  2. AtomicInteger源码分析
  3. CAS

我们知道在进行i=i+1或者i++、++i操作时是线程不安全的。

下面展示代码 count++

public class MainTest {
    private  Integer  count=0;
    public   void add()
    {
        count++;
    }
}

经过javap的编译如下:

"Java\jdk1.8.0_211\bin\javap.exe" -c MainTest.class
Compiled from "MainTest.java"
public class com.txs.common.MainTest {
  public com.txs.common.MainTest();
    Code:
       0: aload_0    //将引用变量this压入栈顶
       1: invokespecial #1                  // Method java/lang/Object."<init>":()V
       4: return

  public void add();
    Code:
       0: iconst_0  //将 数值 0 推送至栈顶。	
       1: istore_1 //将0存入第二个局部变量 ,此时 count=0
       2: iload_1 //将count=0推送至栈顶
       3: iconst_1//将 数值1 推送至栈顶
       4: iadd //将栈顶数值1和count=0相加并将结果压入栈顶,此时栈顶为1
       5: istore_1 //将栈顶数值1存入第二个局部变量,此时count=1
       6: return //从当前方法返回 void
}

Process finished with exit code 0

从以上字节码执行过程发现,jvm在执行count++过程大致分为2步骤,第一步先将count设值为0,然后将count加1再赋值为count。
所以如果线程并发执行的话,多个线程都同时从局部变量取count为0,导致实际结果可能小于最终值。

下面展示代码并发执行最终count值小于1000

package com.txs.juc;

import java.util.concurrent.CountDownLatch;
import java.util.concurrent.atomic.AtomicInteger;

/**
 * juc 自增加并发测试
 */
public class AtomicIntegerTest {

    private static Integer count=0;
 
    private static Integer THREAD_COUNT=1000;

    private static CountDownLatch countDownLatch=new CountDownLatch(THREAD_COUNT);

    public static void main(String[] args) throws Exception {

        for (int i = 0; i < THREAD_COUNT; i++) {
            //启动1000个线程
            new Thread(new Runnable() {
                @Override
                public void run() {

                    add();

                    countDownLatch.countDown();
                }
            }).start();
        }

        //等待所有线程执行结束
        countDownLatch.await();
        System.out.println(count);
//        System.out.println(atomicInteger);

    }

    public  static void add()
    {
        //普通的++
        try {
            Thread.sleep(1);//睡眠1秒
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        count++;
    }


}

执行结果如下:

"D:\Program Files\Java\jdk1.8.0_211\bin\java.exe"
917

Process finished with exit code 0
"D:\Program Files\Java\jdk1.8.0_211\bin\java.exe"
877

Process finished with exit code 0
"D:\Program Files\Java\jdk1.8.0_211\bin\java.exe"
897

Process finished with exit code 0

所以这个时候,怎样保证cout++是线程安全,每次执行都能得到正确的结果呢?
这其中有2种方法,一种是在count++方法上加上同步锁synchronized; 但是这种方法是重量级锁,会导致代码执行比较慢,一旦并发量大,所有浪费的时间都在加锁和解锁上,代码真正执行的时间很短。
第二种方法是使用JUC原子操作Integer类型AtomicInteger方法incrementAndGet来自增操作。

以下是AtomicInteger方法实现安全自增代码

package com.txs.juc;

import java.util.concurrent.CountDownLatch;
import java.util.concurrent.atomic.AtomicInteger;

/**
 * juc 自增加并发测试
 */
public class AtomicIntegerTest {

  
    private static AtomicInteger atomicInteger=new AtomicInteger(0);

    private static Integer THREAD_COUNT=1000;

    private static CountDownLatch countDownLatch=new CountDownLatch(THREAD_COUNT);

    public static void main(String[] args) throws Exception {

        for (int i = 0; i < THREAD_COUNT; i++) {
            //启动1000个线程
            new Thread(new Runnable() {
                @Override
                public void run() {

                    addAtomic();

                    countDownLatch.countDown();
                }
            }).start();
        }

        //等待所有线程执行结束
        countDownLatch.await();
//        System.out.println(count);
        System.out.println(atomicInteger);

    }

    public  static void addAtomic()
    {
       
        try {
            Thread.sleep(1);//睡眠1秒
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        atomicInteger.incrementAndGet();
    }


}

使用AtomicInteger方法执行count++无论怎么执行结果一直是1000

"D:\Program Files\Java\jdk1.8.0_211\bin\java.exe"
1000

Process finished with exit code 0

那么AtomicInteger是怎么既保证线程安全又增加执行效率的呢?
我们查看一下AtomicInteger源码分析一下吧。源码为JDK8下的。
首先我们查看一下AtomicInteger类的变量和静态块代码

*
 * @since 1.5
 * @author Doug Lea
*/
public class AtomicInteger extends Number implements java.io.Serializable {
    private static final long serialVersionUID = 6214790243416807050L;

    // setup to use Unsafe.compareAndSwapInt for updates
    //Unsafe是使用c语言直接操作内存资源的类
    //该类主要提供compareAndSwapInt和getIntVolatile方法操作内存数据
    private static final Unsafe unsafe = Unsafe.getUnsafe();
    //字段value在java内存中的偏移量
    private static final long valueOffset;

    static {
        try {
        //objectFieldOffset获取对象AtomicInteger实例字段value的偏移量
            valueOffset = unsafe.objectFieldOffset
                (AtomicInteger.class.getDeclaredField("value"));
        } catch (Exception ex) { throw new Error(ex); }
    }

    //value就是要进行操作AtomicInteger方法的值
    private volatile int value;

Unsafe是一个不安全的类,因为它可以直接操作java内存资源,Unsafe里面有一些方法直接可以分配内存,但是java内存分配后需要进行释放,如果内存分配使用完不能及时释放会出现内存泄漏问题,所以使用Unsafe方法要注意不要过度使用。
至于什么是字段的偏移量,通过偏移量怎么操作java内存中的值?
可以参考: https://www.jianshu.com/p/cb5e09facfee.
总结一句话是,偏移量就是java对象的实例数据(也就是变量value),在java内存(也就是java堆)中的位置定位。通过偏移量可以获取value在内存中的值。

接下来,我们看看AtomicInteger的自增方法incrementAndGet的源码

  /**
     * Atomically increments by one the current value.
     *
     * @return the updated value
     * //该方法调用了Unsafe 的getAndAddInt方法
     * getAndAddInt方法是循环获取AtomicInteger对象中偏移量为     valueOffset的值并加1,将value值更新并返回value旧值
     */
    public final int incrementAndGet() {
        return unsafe.getAndAddInt(this, valueOffset, 1) + 1;
    }

看看unsafe.getAndAddInt方法

//参数1 var1 是要获取内存的对象
//参数2  var2  是内存对象var1的在内存中的偏移量
//参数3 var4 是要加的数值 为1
public final int getAndAddInt(Object var1, long var2, int var4) {
    //这是一个do..while循环
        int var5;
        do {
        //getIntVolatile方法获取对象var1中偏移量var2的值,
        //强制从内存获取var2偏移量的值,这个方法要求被使用的属性被volatile修饰,而且获取的是Integer类型的值。
            var5 = this.getIntVolatile(var1, var2);
          //compareAndSwapInt 这个数CAS操作
          //操作如下:
          //1、var1和var2是用来获取内存中value的值
          //2、从内存中获取的值和var5进行比较是否相同
          //3、相同,就把内存中value值更新成var5 + var4
          //4、不相同,就返回false,继续执行getIntVolatile方法获取内存值赋值给var5,直到值相同
          //5、执行成功,并更新内存值为var5 + var4,返回true,跳出循环
          //6、最后返回内存旧址
        } while(!this.compareAndSwapInt(var1, var2, var5, var5 + var4));

        return var5;
    }

什么是CAS? 即比较并替换,实现并发算法时常用到的一种技术。CAS操作包含三个操作数——内存位置、预期原值及新值。执行CAS操作的时候,将内存位置的值与预期原值比较,如果相匹配,那么处理器会自动将该位置值更新为新值,否则,返回false,处理器不做任何操作。我们都知道,CAS是一条CPU的原子指令(cmpxchg指令),不会造成所谓的数据不一致问题,Unsafe提供的CAS方法(如compareAndSwapXXX)底层实现即为CPU指令cmpxchg。

猜你喜欢

转载自blog.csdn.net/xxxz123/article/details/107521819