Java并发编程之LockSupport、Unsafe详解

简介

在Java多线程中,当需要阻塞或者唤醒一个线程时,都会使用LockSupport工具类来完成相应的工作。LockSupport定义了一组公共静态方法,这些方法提供了最基本的线程阻塞和唤醒功能,而LockSupport也因此成为了构建同步组件的基础工具。

LockSupport定义了一组以park开头的方法用来阻塞当前线程,以及unpark(Thread)方法来唤醒一个被阻塞的线程,这些方法描述如下:

方法名称

描  述

void park()

阻塞当前线程,如果掉用unpark(Thread)方法或被中断,才能从park()返回

void parkNanos(long nanos)

阻塞当前线程,超时返回,阻塞时间最长不超过nanos纳秒

void parkUntil(long deadline)

阻塞当前线程,直到deadline时间点

void 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));
	}
}

线程dump结果:


这两段代码都是 阻塞当前线程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类详解

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修改内存

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()等方法。

在非Java堆中分配内存

使用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

提供CAS原子操作

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并发编程的艺术》

你可能感兴趣的:(java,java并发编程,Java并发编程源码详解)