Unsafe类功能
java不能直接访问操作系统底层,而是通过本地方法来访问,Unsafe类提供了硬件级别的原子操作,主要提供了以下功能:
- 1、通过Unsafe类可以分配内存,可以释放内存
- 2、可以定位对象某字段的内存位置,也可以修改对象的字段值,就算是它是私有的
- 3、挂起与恢复线程
- 4、CAS操作
分配内存和释放内存
Unsafe类中提供的3个本地方法
allocateMemory
、
reallocateMemory
、
freeMemory
分别用于
分配内存
,
扩充内存
和
释放内存
,与C语言中的3个方法对应。
public native long allocateMemory(long var1);
public native void freeMemory(long var1);
public native Object allocateInstance(Class<?> var1) throws InstantiationException;
定位对象某字段的内存位置以及修改对象的字段值
字段的定位
:
staticFieldOffset()
方法可以获取对象字段的偏移地址,该方法返回给定Field的内存地址偏移量,这个值对于给定的Filed是唯一的且是固定不变的。
public native long staticFieldOffset(Field var1);
Unsafe类中提供很多getxxx方法,就是根据对象地址和字段偏移地址来获取某个字段对应的值,由于字段的类型很多(
byte,short,int,long,float,float,double,char,boolean,引用数据类型等
),所以这种方法也就有很多种,线程安全使用使用
getxxxVolatile()
方法,否则使用
getxxx()
方法,
下面就使用获取int类型的字段的值举例
:
-
getInt()
方法可以获取对象中offset偏移地址对应的
整型Field
的值。/** * 功能:获取的是整形字段的值 int * Object var1:包含要读取字段(Field)的对象 * long var2:该字段的偏移地址 * */ public native int getInt(Object var1, long var2);;
-
getLongVolatile()
方法获取对象中offset偏移地址对应的
整型Field
的值,
支持volatile load语义
/** * 功能:获取的是整形字段的值 long * Object var1:包含要读取字段(Field)的对象 * long var2:该字段的偏移地址 * */ public native long getIntVolatile(Object var1, long var2);
注意:Unsafe类中还提供了一个形参的
getxxx
方法,没有提供一个形参的
getxxxVolatile()
方法,例如:
public native int getInt(long var1);
数组元素定位
:
Unsafe类中有很多以
BASE_OFFSET
结尾的常量,比如ARRAY_INT_BASE_OFFSET,ARRAY_BYTE_BASE_OFFSET等,这些常量值是通过arrayBaseOffset()方法得到的。
arrayBaseOffset()
方法是一个本地方法,可以获取数组第一个元素的偏移地址。
Unsafe类中还有很多以
INDEX_SCALE
结尾的常量,比如 ARRAY_INT_INDEX_SCALE , ARRAY_BYTE_INDEX_SCALE等,这些常量值是通过arrayIndexScale方法得到的。
arrayIndexScale()
方法也是一个本地方法,可以获取数组的转换因子,也就是数组中元素的增量地址。将arrayBaseOffset与arrayIndexScale配合使用,可以定位数组中每个元素在内存中的位置。
public final class Unsafe {
private static final Unsafe theUnsafe;
public static final int INVALID_FIELD_OFFSET = -1;
public static final int ARRAY_BOOLEAN_BASE_OFFSET;
public static final int ARRAY_BYTE_BASE_OFFSET;
public static final int ARRAY_SHORT_BASE_OFFSET;
public static final int ARRAY_CHAR_BASE_OFFSET;
public static final int ARRAY_INT_BASE_OFFSET;
public static final int ARRAY_LONG_BASE_OFFSET;
public static final int ARRAY_FLOAT_BASE_OFFSET;
public static final int ARRAY_DOUBLE_BASE_OFFSET;
public static final int ARRAY_OBJECT_BASE_OFFSET;
public static final int ARRAY_BOOLEAN_INDEX_SCALE;
public static final int ARRAY_BYTE_INDEX_SCALE;
public static final int ARRAY_SHORT_INDEX_SCALE;
public static final int ARRAY_CHAR_INDEX_SCALE;
public static final int ARRAY_INT_INDEX_SCALE;
public static final int ARRAY_LONG_INDEX_SCALE;
public static final int ARRAY_FLOAT_INDEX_SCALE;
public static final int ARRAY_DOUBLE_INDEX_SCALE;
public static final int ARRAY_OBJECT_INDEX_SCALE;
public static final int ADDRESS_SIZE;
public native int arrayBaseOffset(Class<?> var1);
public native int arrayIndexScale(Class<?> var1);
挂起与恢复线程
将一个线程进行挂起是通过
park
方法实现的,调用
park
后,线程将一直阻塞直到超时或者中断等条件出现
,unpark
可以终止一个挂起的线程,使其恢复正常。
public native void park(boolean var1, long var2);
public native void unpark(Object var1);
整个并发框架中对线程的挂起操作被封装在
LockSupport
类中,该类是JUC包下的一个类,LockSupport类中有各种版本pack方法,但最终都调用了
Unsafe.park()
方法。
CAS操作
CAS是通过compareAndSwapXXX方法实现的,如下:
-
compareAndSwapObject
-
compareAndSwapInt
-
compareAndSwapLong
/**
* 比较obj的offset处内存位置中的对象和期望对象,如果相同则更新,此更新是不可中断的。
*
* @param var1 需要更新的对象
* @param var2 obj中obj字段的偏移量,注意obj是一种通称
* @param var4 希望obj字段的值
* @param var5 如果期望值var4与var2相同,设置obj字段的值为这个新值
* @return 如果obj字段的值被更改返回true
*/
public final native boolean compareAndSwapObject(Object var1, long var2, Object var4, Object var5);
/**
* 比较obj的offset处内存位置中的值和期望的值,如果相同则更新,此更新是不可中断的。
*
* @param obj 需要更新的对象
* @param offset obj中整型field的偏移量
* @param expect 希望field中存在的值
* @param update 如果期望值expect与field的当前值相同,设置filed的值为这个新值
* @return 如果field的值被更改返回true
*/
public final native boolean compareAndSwapInt(Object obj, long offset, int expect, int update);
public final native boolean compareAndSwapLong(Object var1, long var2, long var4, long var6);
CAS 操作包含三个操作数 —— 内存位置(offset)、预期原值(expect)和新值(update),如果内存位置的值与预期原值相匹配,那么处理器会自动将该位置值更新为新值,否则,处理器不做任何操作
Java并发包
(java.util.concurrent
)中大量使用了CAS操作,涉及到并发的地方都调用了
sun.misc.Unsafe
类方法进行CAS操作。
Unsafe类功中其他方法介绍
- 注意:这里虽然只是介绍了部分方法,但是大多数的方法使用是相同的,只不过是对不同数据类型进行操作而已!!!
//下面是sun.misc.Unsafe.java类源码
package sun.misc;
import java.lang.reflect.Field;
/***
* 这个类提供了一个更底层的操作并且应该在受信任的代码中使用,可以通过内存地址
* 存取fields,如果给出的内存地址是无效的那么会有一个不确定的运行表现。
*/
public class Unsafe
{
// 单例模式,对外不提供构造方法,使用的时候,使用对用的getxxx方法获取该对象实例
private static Unsafe unsafe = new Unsafe();
/***
* 构造器私有
*/
private Unsafe(){}
/**
* 获取Unsafe的单例
*/
public static Unsafe getUnsafe()
{
SecurityManager sm = System.getSecurityManager();
if (sm != null)
sm.checkPropertiesAccess();
return unsafe;
}
/***
* 返回指定静态字段(Field)的内存地址偏移量
* 这个偏移量对于给定的Field是唯一的,并且后续对该方法的调用都应该返回相同的值。
*
* @param field 需要返回偏移量的Field
* @return Field的偏移量
*/
public native long objectFieldOffset(Field field);
/**
* 在obj的offset位置比较integer field和期望的值,如果相同则更新。这个方法
* 的操作应该是原子的,因此提供了一种不可中断的方式更新integer field。
*
* @param obj 包含要修改field的对象
* @param offset obj中整型field的偏移量
* @param 希望field中存在的值
* @param 如果期望值expect与field的当前值相同,设置filed的值为这个新值
* @return true 如果field的值被更改
*/
public native boolean compareAndSwapInt(Object obj, long offset,
int expect, int update);
/**
* 参考 compareAndSwapInt()
*/
public native boolean compareAndSwapLong(Object obj, long offset,
long expect, long update);
/***
* 在obj的offset位置比较object field和期望的值,如果相同则更新。这个方法
* 的操作应该是原子的,因此提供了一种不可中断的方式更新object field。
*
* @param 包含要修改field的对象
* @param obj中object型field的偏移量
* @param 希望field中存在的值
* @param 如果期望值expect与field的当前值相同,设置filed的值为这个新值
* @return true 如果field的值被更改
*/
public native boolean compareAndSwapObject(Object obj, long offset,
Object expect, Object update);
/***
* 设置obj对象中offset偏移地址对应的整型field的值为指定值。这是一个有序或者
* 有延迟的putIntVolatile方法,并且不保证值的改变被其他线程立即看到。
* 只有在field被volatile关键字修饰并且期望被意外修改的时候使用才有用。
*
* @param 包含需要修改field的对象
* @param obj中整型field的偏移量
* @param field将被设置的新值
* @see putIntVolatile(Object,long,int)
*/
public native void putOrderedInt(Object obj, long offset, int value);
/***
* 参考putOrderedInt()
*/
public native void putOrderedLong(Object obj, long offset, long value);
/***
* 设置obj对象中offset偏移地址对应的object型field的值为指定值。这是一个有序或者
* 有延迟的putObjectVolatile方法,并且不保证值的改变被其他线程立即看到。
* 只有在field被volatile修饰并且期望被意外修改的时候使用才有用。
*
* @param 包含需要修改field的对象
* @param obj中long型field的偏移量
* @param field将被设置的新值
*/
public native void putOrderedObject(Object obj, long offset, Object value);
/***
* 设置obj对象中offset偏移地址对应的整型field的值为指定值,支持volatile store语义
*
* @param 包含需要修改field的对象
* @param obj中整型field的偏移量
* @param field将被设置的新值
*/
public native void putIntVolatile(Object obj, long offset, int value);
/***
* 参考putIntVolatile()
*/
public native void putLongVolatile(Object obj, long offset, long value);
/***
* 设置obj对象中offset偏移地址对应的long型field的值为指定值。
*
* @param 包含需要修改field的对象
* @param obj中long型field的偏移量
* @param field将被设置的新值
* @see #putLongVolatile(Object,long,long)
*/
public native void putLong(Object obj, long offset, long value);
/***
* 获取obj对象中offset偏移地址对应的整型field的值,支持volatile load语义。
*
* @param 包含需要去读取的field的对象
* @param obj中整型field的偏移量
*/
public native int getIntVolatile(Object obj, long offset);
/***
* 参考getIntVolatile()
*/
public native long getLongVolatile(Object obj, long offset);
/***
* 获取obj对象中offset偏移地址对应的long型field的值
*
* @param 包含需要去读取的field的对象
* @param obj中long型field的偏移量
* @see #getLongVolatile(Object,long)
*/
public native long getLong(Object obj, long offset);
/***
* 设置obj对象中offset偏移地址对应的object型field的值为指定值。支持volatile store语义
*
* @param 包含需要修改field的对象
* @param obj中object型field的偏移量
* @param field将被设置的新值
* @see #putObject(Object,long,Object)
*/
public native void putObjectVolatile(Object obj, long offset, Object value);
/***
* 设置obj对象中offset偏移地址对应的object型field的值为指定值。
*
* @param 包含需要修改field的对象
* @param obj中object型field的偏移量
* @param field将被设置的新值
* @see #putObjectVolatile(Object,long,Object)
*/
public native void putObject(Object obj, long offset, Object value);
/***
* 获取obj对象中offset偏移地址对应的object型field的值,支持volatile load语义。
*
* @param 含需要去读取的field的对象
* @param obj中object型field的偏移量
*/
public native Object getObjectVolatile(Object obj, long offset);
/***
* 获取给定数组中第一个元素的偏移地址。
* 为了存取数组中的元素,这个偏移地址与arrayIndexScale方法的非0返回值一起被使用。
* @param 第一个元素地址被获取的class
* @return 数组第一个元素的偏移地址
* @see arrayIndexScale(Class)
*/
public native int arrayBaseOffset(Class arrayClass);
/***
* 获取用户给定数组寻址的换算因子,一个合适的换算因子不能返回的时候(例如:基本类型),
* 返回0,这个返回值能够与arrayBaseOffset一起使用去存取这个数组class中的元素
*
* @param arrayClass the class whose scale factor should be returned.
* @return the scale factor, or zero if not supported for this array class.
*/
public native int arrayIndexScale(Class arrayClass);
/***
* 释放被park创建的在一个线程上的阻塞.这个方法也可以被使用来终止一个先前调用park导致的阻塞.
* 这个操作操作是不安全的,因此线程必须保证是活的,这是java代码不是native代码。
* @param 要解除阻塞的线程
*/
public native void unpark(Thread thread);
/***
* 阻塞一个线程直到unpark出现、线程被中断或者timeout时间到期。如果一个unpark调用已经出现了,
* 这里只计数。timeout为0表示永不过期.当isAbsolute为true时,
* timeout是相对于新纪元之后的毫秒。否则这个值就是超时前的纳秒数。这个方法执行时
* 也可能不合理地返回(没有具体原因)
*
* @param 如果为true timeout的值是一个相对于新纪元之后的毫秒数
* @param 可以是一个要等待的纳秒数,或者是一个相对于新纪元之后的毫秒数直到到达这个时间点
*/
public native void park(boolean isAbsolute, long time);
}