java并发编程之美 学习笔记
JDK的rt.jar
包中的Unsafe
类提供了硬件级别的原子性操作,Unsafe类中绝大多数方法都是native
的,在学习Unsafe之前我们先看下AtomicLong
AtomicLong
public class AtomicLong extends Number implements java.io.Serializable {
//下面会详细介绍Unsafe
private static final Unsafe unsafe = Unsafe.getUnsafe();
private static final long valueOffset;
static {
try {
/**
* 返回:AtomicLong实例中的value属性在内存中的相对偏移量;
* 由于valueOffset是static的,表明了在各个AtomicLong示例中,他们value属性的相对偏移量都是相同的;
*
* 因为Unsafe就是根据内存偏移地址获取数据的原值的, 偏移量可以简单理解为指针指向该变量的内存地址。
*/
valueOffset = unsafe.objectFieldOffset
(AtomicLong.class.getDeclaredField("value"));
} catch (Exception ex) { throw new Error(ex); }
}
//value使用volatile修饰,直接从共享内存中操作变量,保证多线程之间看到的value值是同一份。
private volatile long value;
public AtomicLong(long initialValue) {
value = initialValue;
}
public final boolean compareAndSet(long expect, long update) {
//获取this对象相对偏移量valueOffset 的预期值是否与expect值相同,如果相同则更新为update值
return unsafe.compareAndSwapLong(this, valueOffset, expect, update);
}
}
Unsafe APIs
Unsafe.getUnsafe()
public final class Unsafe {
private static final Unsafe theUnsafe;
public static Unsafe getUnsafe() {
Class var0 = Reflection.getCallerClass();
if (!VM.isSystemDomainLoader(var0.getClassLoader())) {
throw new SecurityException("Unsafe");
} else {
//返回 theUnsafe
return theUnsafe;
}
}
static {
registerNatives();
Reflection.registerMethodsToFilter(Unsafe.class, new String[]{"getUnsafe"});
//static{} ,表明theUnsafe在当前jvm中独一份。
theUnsafe = new Unsafe();
//略....
}
}
Long相关 APIs
public final class Unsafe {
/**
* @param var1 ;AtomicLong 对象obj
* @param var2 : offset偏移量
* @return 返回obj之前的值
*/
public native long getLong(Object var1, long var2);
/**
* @param var1 : obj
* @param var2 :offset偏移量
* @param var4 :newValue
*/
public native void putLong(Object var1, long var2, long var4);
/**
* @param var1 :obj
* @param var2 : offset偏移量
* @param var4 : expect即之前的值.
* @param var6 : update即即将更新的值
* @return 返回更新成功or失败;
*/
public final native boolean compareAndSwapLong(Object var1, long var2, long var4, long var6);
//同getLong(Object var1, long var2),支持volatile语义,即直接从主内存中获取(不从工作内存读取)
public native long getLongVolatile(Object var1, long var2);
//同putLong(Object var1, long var2, long var4),支持volatile语义,即修改可被其他线程立即可见;
public native void putLongVolatile(Object var1, long var2, long var4);
//这是一个有延迟的putLongvolatile方法 ,并且不保证值修改对其他线程立刻可见 。
public native void putOrderedLong(Object var1, long var2, long var4);
/**
* 将原值+addValue
* @param var1 : obj
* @param var2 : offset偏移量
* @param var4 : addValue
* @return 返回之前的值
*/
public final long getAndAddLong(Object var1, long var2, long var4) {
long var6;
do {
//获取之前的值
var6 = this.getLongVolatile(var1, var2);
//考虑到多线程CAS失败时需要重试
//while循环尝试调用compareAndSwapLong方法,直至成功
//var6 + var4 : 将原先值增加+addValue
} while(!this.compareAndSwapLong(var1, var2, var6, var6 + var4));
return var6;
}
//参照getAndAddLong(Object var1, long var2, long var4)
public final long getAndSetLong(Object var1, long var2, long var4) {
long var6;
do {
var6 = this.getLongVolatile(var1, var2);
} while(!this.compareAndSwapLong(var1, var2, var6, var4));
return var6;
}
}
数组相关APIs
public final class Unsafe {
public static final int ARRAY_LONG_BASE_OFFSET;
public static final int ARRAY_LONG_INDEX_SCALE;
static {
//略....
theUnsafe = new Unsafe();
ARRAY_LONG_BASE_OFFSET = theUnsafe.arrayBaseOffset(long[].class);
ARRAY_LONG_INDEX_SCALE = theUnsafe.arrayIndexScale(long[].class);
//略...
}
//获取数组中第一个元素的地址
public native int arrayBaseOffset(Class<?> var1);
//获取数组中一个元素占用的字节
public native int arrayIndexScale(Class<?> var1);
}
其他APIs
/**
* 阻塞当前线程
* @param var1 : isAbsolute,表示是否为绝对时间
* @param var2 : time
*
* 当isAbsolute=false时,即不是绝对时间,为相对时间,此时时间单位为nanos
* - time=0,表示一直阻塞
* - time>0,表示time时间段后,阻塞线程会被唤醒
*
* 当isAbsolute=true时,即是绝对时间:
* - time>0, 这里time是个绝对时间,是将某个时间转换计算为ms后的值,表示当前则色线程到某一个时间点后会被唤醒
*/
public native void park(boolean var1, long var2);
/**
* 唤醒调用 park 后阻塞的线程。
* @param var1 : Thread obj
*/
public native void unpark(Object var1);
//JDK会在执行这三个方法时插入StoreStore内存屏障,避免发生写操作重排序。
public native void putOrderedObject(Object var1, long var2, Object var4);
public native void putOrderedInt(Object var1, long var2, int var4);
public native void putOrderedLong(Object var1, long var2, long var4);
使用Unsafe
使用Unsafe
public class TestUnsafe {
static final Unsafe unsafe = Unsafe.getUnsafe();
static final long stateOffset;
private volatile long state = 0;
static {
try {
stateOffset = unsafe.objectFieldOffset(TestUnsafe.class.getDeclaredField("state"));
} catch (NoSuchFieldException e) {
e.printStackTrace();
//注意这里抛出Error,否则无法完成给final变量初始化.
throw new Error(e);
}
}
public static void main(String[] args) {
TestUnsafe test = new TestUnsafe();
Boolean success = unsafe.compareAndSwapLong(test,stateOffset,2L,3L);
System.out.println(success);
}
}
运行此程序会报错,
重新回顾Unsafe代码:
//sun.misc.Unsafe
@CallerSensitive
public static Unsafe getUnsafe() {
Class var0 = Reflection.getCallerClass();
//var0的classLoader必须为SystemDomainLoader,
if (!VM.isSystemDomainLoader(var0.getClassLoader())) {
throw new SecurityException("Unsafe");
} else {
return theUnsafe;
}
}
//sun.misc.VM
public static boolean isSystemDomainLoader(ClassLoader var0) {
return var0 == null;
}
分析上述的代码,可知,var0的classLoader必须为null,才会成功返回Unsafe实例,否则则抛出如上图的异常。
回顾下类加载器
Java中类加载器大致可以分成两种,一种是内建的类加载器也就是Java自带的类加载器,一种是用户编写的类加载器。除了系统提供的类加载器意外,开发人员可以通过继承 java.lang.ClassLoader类的方式来实现自己的类加载器。
系统提供的类加载器有下面三个:
- 引导类加载器(bootstrap class loader):用来加载Java核心类,并不继承java.lang.ClassLoader(一般一种语言的引导类加载器都不是本语言写的,否则就陷入了鸡生蛋蛋生鸡的问题,例如JAVA的引导类加载器就是C++编写),这个类不遵守下面的类加载器的规则,没有父类,也没有子类,外部也无法访问,属于纯粹JVM自己使用。他加载的目录是<JAVA_HOME>/jre/lib/rt.jar 或者使用配置-Xbootclasspath指定目录。
*- 扩展类加载器(extension class loader):用来加载JAVA的扩展类,JAVA虚拟机的实现会提供一个扩展目录,该加载器加载指定目录下的所有的类。目录在<JAVA_HOME>/jre/lib/ext或java.ext.dirs变量中指定,也可以使用 System.getProperty(‘java.ext.dirs’)来获取加载的目录,可以使用-Djava.ext.dirs配置来指定加载目录,该类由sun.misc.Launcher$ExtClassLoader 来实现。
*- 系统类加载器(system class loader):也称为Apps类加载器,根据Java的类路径(环境的classpath) 来加载Java类。一般Java应用的类都是由他来加载的,可以通过ClassLoader.getSystemClassLoader() 来获取,这个加载器由sun.misc.Launcher$AppClassLoader来实现,可以更改classpath或者使用配置 -Djava.class.path=-cp 或者-Djava.class.path=-classpath来更改加载目录。
经过诸上的一系列分析,由于TestUnsafe
的类加载器为AppClassLoader
,所以并不允许使用Unsafe
实例.
反射获取Unsafe
既然常规方法不允许使用Unsafe
,那么使用点黑科技,利用反射来获取Unsafe实例:
public class TestUnsafe {
// static final Unsafe unsafe = Unsafe.getUnsafe();
static final Unsafe unsafe;
static final long stateOffset;
private volatile long state = 0;
static {
try {
Field theUnsafeField = Unsafe.class.getDeclaredField("theUnsafe");
//暴力反射
theUnsafeField.setAccessible(true);
unsafe = (Unsafe) theUnsafeField.get(null);
stateOffset = unsafe.objectFieldOffset(TestUnsafe.class.getDeclaredField("state"));
} catch (Exception e) {
//注意这里抛出Error,否则无法完成给final变量初始化.
throw new Error(e);
}
}
}
测试:
public static void main(String[] args) {
TestUnsafe test = new TestUnsafe();
/**
* except: 1
* update: 2
* 当前值为 0
*
* 打印false
*/
System.out.println(unsafe.compareAndSwapLong(test,stateOffset,1L,2L));
/**
* except: 0
* update: 2
* 当前值为 0
*
* 打印true
*/
System.out.println(unsafe.compareAndSwapLong(test,stateOffset,0L,2L));
/**
* except: 2
* update: 3
* 当前值为 2
*
* 打印true
*/
System.out.println(unsafe.compareAndSwapLong(test,stateOffset,2L,3L));
}