博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
并发编程JUC包源码分析——从AtomicInteger到Unafe
阅读量:3556 次
发布时间:2019-05-20

本文共 5714 字,大约阅读时间需要 19 分钟。

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 9933Process finished with exit code 0*/

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

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 10000Process 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方法的源码如下:

@CallerSensitivepublic 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字段的偏移量是:12TestUnsafe.date字段的值是:Thu Nov 22 17:14:48 CST 2018Process finished with exit code 0

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

你可能感兴趣的文章
字节码文件(Class文件)
查看>>
java中的IO流(一)----概述
查看>>
StringBuilder
查看>>
集合,Collection
查看>>
泛型详解
查看>>
泛型实现斗地主
查看>>
List集合
查看>>
ArrayList集合,LinkedList集合,Vector集合
查看>>
HashSet集合
查看>>
并发与并行,线程与进程
查看>>
方法引用,通过对象名引用成员变量
查看>>
常用工具类 Math:数学计算 Random:生成伪随机数 SecureRandom:生成安全的随机数 2020-2-13
查看>>
Java的异常Exception 2020-2-13
查看>>
Java标准库定义的常用异常,自定义异常 2020-2-15
查看>>
Java问题百度/Google记录 2020-2-16
查看>>
【PADS9.5】9,对比ECO核心板,Router移动元件后布线消失,Router找不到自动布线策略文件丢失或损坏
查看>>
【STM32+w5500汇总】23,HTTP_Client 连接到ONENET上传了一段数据之后会断开,数据上传格式的设置
查看>>
【STM32+W5500+MQTT】24,所有功能都可以通过API函数的调用来实现;HTTP接入ONENET,API开发手册和打包函数,串口软件HTTP连接服务器上传数据,2018年12月28日
查看>>
【STM32+W5500+HTTPClient】25,路由器DHCP租赁IP时间为2h,NetBios可以很好的解决IP变化的问题,DNS,2018年12月25日
查看>>
【STM32+MQTT+ONENET】26,MQTT协议接入OneNET
查看>>