在Java多线程中,当需要阻塞或者唤醒一个线程时,都会使用LockSupport工具类来完成相应的工作。LockSupport定义了一组公共静态方法,这些方法提供了最基本的线程阻塞和唤醒功能,而LockSupport也因此成为了构建同步组件的基础工具。
LockSupport定义了一组以park开头的方法用来阻塞当前线程,以及unpark(Thread)方法来唤醒一个被阻塞的线程,这些方法描述如下:
在Java 6中,LockSupport增加了park(Object blocker)、parkNanos(Object blocker, long nanos)、parkUntil(Object blocker, long deadline)这3个方法,用于实现阻塞当前线程的功能,其中参数blocker是用来标识当前线程在等待的对象,该对象主要用于问题排查和系统监控。下面的示例中,将对比parkNanos(long nanos)和parkNanos(Object blocker, long nanos)方法来展示阻塞对象blocker的用处。
采用parkNanos(long nanos)阻塞线程:
public class LockSupportTest { public static void main(String[] args) { LockSupport.parkNanos(TimeUnit.SECONDS.toNanos(20)); } }
线程dump结果:
采用parkNanos(Object blocker, long nanos)阻塞线程:
public class LockSupportTest { public static void main(String[] args) { LockSupport.parkNanos(new Object(), TimeUnit.SECONDS.toNanos(20)); } }
这两段代码都是 阻塞当前线程20秒,从上面的dump结果可以看出,有阻塞对象的parkNanos方法能够传递给开发人员更多的现场信息。这是由于在Java 5之前,当线程使用synchronized关键字阻塞在一个对象上时,通过线程dump能够看到该线程的阻塞对象,而Java 5推出的Lock等并发工具却遗漏了这一点,致使在线程dump时无法提供阻塞对象的信息。因此,在Java 6中,LockSupport新增了上述3个含有阻塞对象的方法,用以替代原有的park方法。
通过源码可以发现,LockSupport的park和unpark方法都是通过sun.misc.Unsafe类的park和unpark方法实现的,那下面我们对sun.misc.Unsafe类的源码进行进一步解析。
Unsafe类就和它的名字一样,是一个比较危险的类,它主要用于执行低级别、不安全的方法。尽管这个类和所有的方法都是公开的(public),但是这个类的使用仍然受限,你无法在自己的java程序中直接使用该类,因为只有授信的代码才能获得该类的实例。如果我们要使用Unsafe类,首先需要获取Unsafe类的对象,但是它的构造函数是private的:
private Unsafe() {}
我们只能通过Unsafe的getUnsafe()方法获取该类的对象:
@CallerSensitive public static Unsafe getUnsafe() { Class<?> caller = Reflection.getCallerClass(); if (!VM.isSystemDomainLoader(caller.getClassLoader())) throw new SecurityException("Unsafe"); return theUnsafe; }
Unsafe类是比较危险的,它只有在授信代码中才会返回theUnsafe对象,否则,抛出SecurityException异常,那什么是授信的代码呢?我们看一看getClassLoader()方法:
@CallerSensitive public ClassLoader getClassLoader() { ClassLoader cl = getClassLoader0(); if (cl == null) return null; SecurityManager sm = System.getSecurityManager(); if (sm != null) { ClassLoader.checkClassLoaderPermission(cl, Reflection.getCallerClass()); } return cl; }
该方法返回加载该类的类加载器,如果是被Bootstrap ClassLoader加载的类,则cl为null,然后我们再看VM.isSystemDomainLoader(ClassLoader)方法:
public static boolean isSystemDomainLoader(ClassLoader loader) { return loader == null; }
若类加载器为null,则返回true,即该代码为授信代码。所以,只要代码是被Bootstrap ClassLoader类加载器加载的类就是授信代码了。
我们知道Bootstrap ClassLoader类加载器会加载-Xbootclasspath参数所指定的路径中的类,所以,我们可以修改- Xbootclasspath参数,将我们的代码所在的路径添加进去,那我们的代码就可以使用Unsafe类了;或者也可以使用反射从 Unsafe类上得到它私有的Unsafe实例。如下所示:
public class UnsafeTest { public static void main(String[] args) throws Exception { Field field = Unsafe.class.getDeclaredField("theUnsafe"); field.setAccessible(true); Unsafe unsafe = (Unsafe) field.get(null); } }
我们先来看一下Unsafe的park方法和unpark方法:
public native void park(boolean isAbsolute, long time); public native void unpark(Object thread);
这两个类都是声明native的,其具体实现要去看Java虚拟机的源代码,这里就不再看源码了,有兴趣的可以看看。我们接下来看一看Unsafe类的其他方法,看一看Unsafe还能做什么危险操作。
Unsafe类的putInt()和getInt()方法可以直接修改内存,如下面这一段代码:
public static void unsafePutGetInt() throws Exception { class Student { private int age = 5; public int getAge() { return age; } } Student student = new Student(); System.out.println(student.getAge()); Field field = student.getClass().getDeclaredField("age"); unsafe.putInt(student, unsafe.objectFieldOffset(field), 10); System.out.println(student.getAge()); }
运行结果:
5 10
可以看到,我们通过Unsafe直接修改了类的private变量值。类似的还有getBoolean()、putBoolean()、getChar()、putChar()等方法。
使用new关键字分配的内存会在堆中,并且对象的生命周期内,会被垃圾回收器管理。Unsafe类通过allocateMemory(long)方法分配的内存,不受Integer.MAX_VALUE的限制,并且分配在非堆内存,使用它时,需要非常谨慎,该部分内存需要手动回收,否则会产生内存泄露;非法的地址访问时,会导致Java虚拟机崩溃。在需要分配大的连续区域、实时编程时,可以使用该方式,java的nio使用了这一方法。
public static void unsafeAllocateMemory() throws Exception { int BYTE = 1; long address = unsafe.allocateMemory(BYTE); unsafe.putByte(address, (byte) 10); byte num = unsafe.getByte(address); System.out.println(num); unsafe.freeMemory(address); }
10
Unsafe类中提供了compareAndSwapObject()、compareAndSwapInt()和compareAndSwapLong()这三个方法用来实现对应的CAS原子操作。在Java的并发编程中用到的CAS操作都是调用的Unsafe类的相关方法。我们以Unsafe实现一个自定义原子类:
public static void unsafeCAS() throws Exception { class MyAutomicInteger { private volatile int value = 0; private Unsafe unsafe; private long offset; public MyAutomicInteger(Unsafe unsafe) throws Exception { this.unsafe = unsafe; this.offset = unsafe.objectFieldOffset(MyAutomicInteger.class.getDeclaredField("value")); } public void increment() { int oldValue = value; for (;;) { if (unsafe.compareAndSwapInt(this, offset, oldValue, oldValue + 1)) { break; } oldValue = value; } } public int getAndIncrement() { int oldValue = value; for (;;) { if (unsafe.compareAndSwapInt(this, offset, oldValue, oldValue + 1)) { return oldValue; } oldValue = value; } } public int getValue() { return value; } } MyAutomicInteger myAutomicInteger = new MyAutomicInteger(unsafe); myAutomicInteger.increment(); System.out.println(myAutomicInteger.getValue()); for (int i = 0; i < 5; i++) { System.out.println(myAutomicInteger.getAndIncrement()); } System.out.println(myAutomicInteger.getValue()); }
1 1 2 3 4 5 6
可以看到,通过Unsafe类可以实现很多有趣的功能,这些方法都是比较底层的方法,而且效率比较高,但是使用起来却比较危险,因为Unsafe类中的方法与我们通常的用法相悖,比如,通过Unsafe类直接修改其他类的parivate变量,直接分配堆外内存等等,这很像c语言的malloc()方法。在平常的开发当中,并不建议直接使用Unsafe类。
方腾飞:《Java并发编程的艺术》
原文链接:https://blog.csdn.net/qq_38293564/article/details/80512758?utm_medium=distribute.wap_relevant.none-task-blog-BlogCommendFromMachineLearnPai2-1.nonecase&depth_1-utm_source=distribute.wap_relevant.none-task-blog-BlogCommendFromMachineLearnPai2-1.nonecase