Unsafe类

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));
	}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值