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供我们使用。
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包下大部分类的源码分析,敬请期待,加作者微信或关注作者的微信公众号,可以得到更多后端技术。