并发编程JUC包源码分析——从AtomicInteger到Unafe

AtomticInteger类

我们都知道,在多线程环境中操作一个Integer类型的数据会产生数据不一致现象,比如i++操作,这是因为i++操作并不是一个原子操作,来看下面的例子:

public class Main{
    public static int i = 0;
    public static void main(String[] args) throws Exception {

       for(int a=0;a<100;a++){
           new Thread(new Runnable() {
               @Override
               public void run() {
                   for(int j = 0;j<100;j++){
                       i++;
                   }
               }
           }).start();
       }
       Thread.sleep(2000);
        System.out.println(i);
    }
}

/*
"D:\Program Files\Java\jdk1.8.0_45\bin\java.exe" "-javaagent:C:\Program 

9933

Process finished with exit code 0
*/

定义一个全局的变量i,在main方法中启动100个线程,每个线程对i自增100次,如果在单线程情况下,i的最终结果应该是10000,但实际结果是一个<=10000数,怎么避免这个问题呢?加锁效率太低,还好,JDK为我们提供了一系列的API供我们使用。

《并发编程JUC包源码分析——从AtomicInteger到Unafe》

Integer对应的是AtomicInteger,long对应于AtomicLong。再来看下面的例子

public class Main{
    public static AtomicInteger i = new AtomicInteger(0);
    public static void main(String[] args) throws Exception {
       for(int a=0;a<100;a++){
           new Thread(new Runnable() {
               @Override
               public void run() {
                   for(int j = 0;j<100;j++){
                       i.incrementAndGet();
                   }
               }
           }).start();
       }
       Thread.sleep(2000);
        System.out.println(i);
    }
}
/*
"D:\Program Files\Java\jdk1.8.0_45\bin\java.exe" "-javaagent:C:\Program 

10000
Process finished with exit code 0
*/

将int类型换成AtomicInteger调用incrementAndGet()方法自增,没有任何问题,结果正确。来看一下AtomicInteger.incrementAndGet()源码

public final int incrementAndGet() {
   return unsafe.getAndAddInt(this, valueOffset, 1) + 1;
}

内部调用的其实是unsafe.getAndAddInt()方法。今天的重点来了,下面来为大家介绍jt,jar包下鼎鼎大名的Unsafe类。

Unsafe类

位于sun.misc包下的Unsafe类,从操作系统层面为开发者提供了大量原子操作的API。大家可以看源码,Unsafe类的大部分方法都是native方法,而一些public方法都是调用native的方法。比如AtomticInteger.incrementAndGet()实际调用Unsafe类的compareAndSwapInt方法。

画外音:该方法很重要,很多线程同部工具底层都依赖于Unsafe的compareAndSwapInt()方法。

 Unsafe类中对于该类方法的定义如下

public final native boolean compareAndSwapObject(Object var1, long var2, Object var4, Object var5);

public final native boolean compareAndSwapInt(Object var1, long var2, int var4, int var5);

public final native boolean compareAndSwapLong(Object var1, long var2, long var4, long var6);

这类方法,都是native方法,返回boolean,我们可以这么理解:

public final native boolean compareAndSwapXXX(Object obj, long offset, xxx oldValue, xxx newValue);

对于一个对象obj,如果在该对象内部偏移量为offset的变量的值为oldValue,则将oldValue更新为newValue返回true,否则返回false。再来看该些方法的调用者

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;
}

通过一个看上去像死循环的代码实现自增运算,这叫自旋。如果while条件为false,该线程修改这个var5字段的值时,其他线程已经先于这个线程修改,修改失败继续循环,每次都获取最新的var5的值,直到修改成功退出循环返回。

画外音:通过循坏达到自旋,总会有性能损失 

使用Unsafe类

我们再来看AtomicInteger类内部的结构

public class AtomicInteger extends Number implements java.io.Serializable {
    private static final long serialVersionUID = 6214790243416807050L;

    // setup to use Unsafe.compareAndSwapInt for updates
    private static final Unsafe unsafe = Unsafe.getUnsafe();
    private static final long valueOffset;

    static {
        try {
            valueOffset = unsafe.objectFieldOffset
                (AtomicInteger.class.getDeclaredField("value"));
        } catch (Exception ex) { throw new Error(ex); }
    }

    private volatile int value;
    .
    .
    .

}

value:表示AtomicInteger的值。

valueOffset:value字段在AtomicInteger类中的偏移量,通过unsafe.objectFieldOffset()方法获取。

unsafe:JDK的Unsafe类。

既然Unsafe类这么牛掰,那我们就来使用以下吧!

public class TestUnsafe {
    public Date date = new Date();
    public static void main(String[] args) throws Exception {
        TestUnsafe obj = new TestUnsafe();
        Unsafe unsafe = Unsafe.getUnsafe();
        long offset = unsafe.objectFieldOffset(TestUnsafe.class.getDeclaredField("date"));
        System.out.println(offset);
        System.out.println(unsafe.getObjectVolatile(obj,offset));
    }
}

Exception in thread "main" java.lang.SecurityException: Unsafe
	at sun.misc.Unsafe.getUnsafe(Unsafe.java:90)
	at com.banma.ThreadLocalDemo.TestUnsafe.main(TestUnsafe.java:17)

Process finished with exit code 1

我通过Unsafe.getUnsafe()获得Unsafe的实例并获取TestUnsafe中date字段的偏移量和值,运行程序如下报错,继续点击进去看Unsafe.getUnsafe方法的源码如下:

@CallerSensitive
public static Unsafe getUnsafe() {
    Class var0 = Reflection.getCallerClass();    //获得调用者的类,本例中是TestUnsafe
/*
    debug得知,var0.getClassLoader()==null
    如果该类是由BootStrap类加载器加载的,classLoader为null   
    很明显,TestUnsafe不是由BootStrap加载的,所以会抛异常
    为什么AtomticInteger可以正常使用Unsafe呢?因为AtomicInteger和Unsafe都在rt.jar下
*/
    if (!VM.isSystemDomainLoader(var0.getClassLoader())) {
        throw new SecurityException("Unsafe");
    } else {
        return theUnsafe;
    }
}
public static boolean isSystemDomainLoader(ClassLoader var0) {
   return var0 == null;
}

那用户该怎么正确使用Unsafe呢?通过JAVA中的神器——反射,代码如下

public class TestUnsafe {
    public Date date = new Date();
    public static void main(String[] args) throws Exception {
        TestUnsafe obj = new TestUnsafe();
        Class clazz = Class.forName("sun.misc.Unsafe");
        Field field = clazz.getDeclaredField("theUnsafe");
        field.setAccessible(true);
        Unsafe unsafe = (Unsafe)field.get(null);
        long offset = unsafe.objectFieldOffset(TestUnsafe.class.getDeclaredField("date"));
        Date d = (Date) unsafe.getObjectVolatile(obj,offset);
        System.out.println("TestUnsafe.date字段的偏移量是:"+offset);
        System.out.println("TestUnsafe.date字段的值是:"+d);
    }
}

运行结果
-----------------------------------------------------
TestUnsafe.date字段的偏移量是:12
TestUnsafe.date字段的值是:Thu Nov 22 17:14:48 CST 2018

Process finished with exit code 0

好了,本文给大家分享的内容就是这些,虽然比较简单但是对于以后的多线程学习非常重要,在接下来的时间里,我会陆续给大家分享JUC包下大部分类的源码分析,敬请期待,加作者微信或关注作者的微信公众号,可以得到更多后端技术。

《并发编程JUC包源码分析——从AtomicInteger到Unafe》《并发编程JUC包源码分析——从AtomicInteger到Unafe》

    原文作者:JUC
    原文地址: https://blog.csdn.net/nuoWei_SenLin/article/details/84338154
    本文转自网络文章,转载此文章仅为分享知识,如有侵权,请联系博主进行删除。
点赞