本文共 5714 字,大约阅读时间需要 19 分钟。
我们都知道,在多线程环境中操作一个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类。
位于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的值,直到修改成功退出循环返回。
画外音:通过循坏达到自旋,总会有性能损失
我们再来看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包下大部分类的源码分析,敬请期待,加作者微信或关注作者的微信公众号,可以得到更多后端技术。