我们以前在对线程进行阻塞与唤醒的时候经常会使用Object类中的wait()和notify(),其实除了这个方式之外,Java中还提供了另外一种的方式来对线程进行挂起和恢复:LockSupport。
案例演示
该类中有两个非常核心的方法park()和unpark()方法分别用于对线程进行挂起和恢复的操作,我们知道如果对线程进行了挂起的操作的话,则当前线程处于等待的状态。下面我们来模拟一下:当一个线程的任务执行完成之后再接着执行第二个线程,这种情况在我们的平时的生活中是非常多见的。
public static void main(String[] args) {
//现在的情况就是当线程1的任务执行完毕之后再接着执行线程2的任务。
final Thread thread2 = new Thread(new Runnable() {
@Override
public void run() {
//首先我们将线程2进行挂起,然后等线程1执行完成之后再恢复
LockSupport.park();
System.out.println("开始执行线程2任务");
num = num + 9;
System.out.println("Thread.." + Thread.currentThread().getName() + "...number..." + num);
}
});
Thread thread1 = new Thread(new Runnable() {
@Override
public void run() {
System.out.println("开始执行线程1任务");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
num = num + 2;
System.out.println("Thread.." + Thread.currentThread().getName() + "...number.." + num);
//恢复线程2的执行。
LockSupport.unpark(thread2);
}
});
thread1.start();
thread2.start();
}
上面仅仅只是LockSupport的一个方面的例子实际应用起来还是比较简单的,我们只要记住park()是将当前的线程挂起,挂起之后该线程将无法进行继续执行任务的。unpark()是将某个线程进行唤醒能继续执行任务的,这个是比较好理解的。LockSupport有点类似于二元信号量(只有1个许可证可供使用),如果这个许可证还没有被占用,当前线程获取许可证继续执行;如果许可证已经被占用,当前线程阻塞,等待获取许可。
public static void main(String[] args) {
LockSupport.park();
System.out.println("该行文字将打印不出来");
}
在运行上面的代码的时候主线程会被阻塞的,也就是说那个文字是不能打印出来的。我们也可以先释放许可,然后再获取许可,主线程就能够正常的终止了;LockSupport的许可一般来说是对应的,如果多次的unpark(),只有一次的park也不会出现什么问题;但是多次的unpark(),多次的park()就会有问题的。接下来再看看例子:
public static void main(String[] args) {
LockSupport.unpark(Thread.currentThread());
LockSupport.park();
System.out.println("该行文字将打印出来");
}
下面我们将多次的调用park()方法,但是只调用一次的unpark()方法来进行的操作。
public static void main(String[] args) {
LockSupport.unpark(Thread.currentThread());
LockSupport.park();
System.out.println("该行文字将打印出来");
//我们多次的调用了park(),但是我们只是调用了一次的unpark()
LockSupport.park();
System.out.println("该行文字将打印不出来");
}
原理分析
LockSupport的底层最后还是使用了Unsafe.java去实现的park()和unpark(),对于Unsafe介绍我们在之前的博客是已经介绍过了,该类是Java并发中的核心,基本上所有的多线程的并发都是与该类有关的,下面我们来看看LockSupport的源代码:
public class LockSupport {
private LockSupport() {} // Cannot be instantiated.
// Hotspot implementation via intrinsics API
private static final sun.misc.Unsafe U = sun.misc.Unsafe.getUnsafe();
private static final long PARKBLOCKER;
private static final long SECONDARY;
static {
try {
PARKBLOCKER = U.objectFieldOffset
(Thread.class.getDeclaredField("parkBlocker"));
SECONDARY = U.objectFieldOffset
(Thread.class.getDeclaredField("threadLocalRandomSecondarySeed"));
} catch (ReflectiveOperationException e) {
throw new Error(e);
}
}
public static void park() {
U.park(false, 0L);
}
public static void unpark(Thread thread) {
if (thread != null)
U.unpark(thread);
}
public static void park(Object blocker) {
Thread t = Thread.currentThread();
setBlocker(t, blocker);
U.park(false, 0L);
setBlocker(t, null);
}
public static void parkNanos(long nanos) {
if (nanos > 0)
U.park(false, nanos);
}
}
从上面的代码中我们看到最后调用的还是Unsafe.java的park()和unpark()方法来实现的。下面我们就直接找到sun.misc包下的Unsafe类
public void park(boolean absolute, long time) {
if (absolute) {
Thread.currentThread().parkUntil$(time);
} else {
Thread.currentThread().parkFor$(time);
}
}
public void unpark(Object obj) {
if (obj instanceof Thread) {
((Thread) obj).unpark$();
} else {
throw new IllegalArgumentException("valid for Threads only");
}
}
当我们翻开Unsafe类中的park()方法和unpark()方法的时候,我们瞬间是不是觉得非常的吃惊?因为最后还是调用的Thread里面的一些方法。下面我们接着看Thread的方法。
public class Thread implements Runnable
{
private final Object lock = new Object();
/** the park state of the thread */
private int parkState = ParkState.UNPARKED;
public final void parkFor$(long nanos) {
synchronized(lock) {
switch (parkState) {
case ParkState.PREEMPTIVELY_UNPARKED: {
parkState = ParkState.UNPARKED;
break;
}
case ParkState.UNPARKED: {
long millis = nanos / NANOS_PER_MILLI;
nanos %= NANOS_PER_MILLI;
parkState = ParkState.PARKED;
try {
lock.wait(millis, (int) nanos);
} catch (InterruptedException ex) {
interrupt();
} finally {
/*
* Note: If parkState manages to become
* PREEMPTIVELY_UNPARKED before hitting this
* code, it should left in that state.
*/
if (parkState == ParkState.PARKED) {
parkState = ParkState.UNPARKED;
}
}
break;
}
default /*parked*/: {
throw new AssertionError("Attempt to repark");
}
}
}
}
public final void parkUntil$(long time) {
synchronized(lock) {
long delayMillis = time - System.currentTimeMillis();
if (delayMillis <= 0) {
parkState = ParkState.UNPARKED;
} else {
parkFor$(delayMillis * NANOS_PER_MILLI);
}
}
}
public final void unpark$() {
synchronized(lock) {
switch (parkState) {
case ParkState.PREEMPTIVELY_UNPARKED: {
/*
* Nothing to do in this case: By definition, a
* preemptively unparked thread is to remain in
* the preemptively unparked state if it is told
* to unpark.
*/
break;
}
case ParkState.UNPARKED: {
parkState = ParkState.PREEMPTIVELY_UNPARKED;
break;
}
default /*parked*/: {
parkState = ParkState.UNPARKED;
lock.notifyAll();
break;
}
}
}
}
}
通过上面对Unsafe的代码追溯到Thread中的时候,我们能很明显的知道了Unsafe中的unpark和park()对线程的唤醒和挂起其实最后都是调用了线程本身中的unpark()和park()方法的。下面我们依次的来分析上述的几种情况:
调用LockSupport.park()会阻塞当前的线程
在上面Thread的park()方法中,我们可以很明显地看到初次使用park()方法的话,最后则会调用 ParkState.UNPARKED分支的,也就是说最后还是会调用Object.java的wait(long millis, int nanos)方法来挂起当前的线程中的。第一次调用unpark(),然后再调用park()不会当前的线程
我们在Thread的unpark ()方法中可以看到,如果我们第一次调用调用unpark()的话,因为初始的parkState为UNPARKED则会进入第二个分支的。所以parkState则会设置为ParkState.PREEMPTIVELYUNPARKED。当我们再次调用park()的时候,则parkState的状态为UNPARKED了,∗∗如果我们对park()调用两次的话,因为前一次将状态设置为了UNPARKED了,第二次的话则会进入到parkFor ()第二个分支中了,则最后还是调了Object.java的wait()去挂起当前的线程**
综上所述发现LockSupport的park()和unpark()最后还是调用了Object的wait()和notifyAll()来达到对线程进行挂起和唤醒的操作的,只是人家把这些都封装的更加方便和实用了,不需要我们重复的去造轮子了。所以对线程的挂起和恢复最底层的还是在Object的wait()和notifyAll()中,其他的类更多的则是对它们的一个封装的。下面看看具体的方法调用:
总结
通过我们对LockSupport的park()和unpark()方法分析发现其内部的原理最后还是调用了Object的wait()和notifyAll()来对线程进行挂起和恢复的。如果以后我们以后还需要继续深入底层去理解的话,则应该往Object的jni方法中去了解到。但是LockSupport的封装确实是非常的方便我们对线程进行挂起和恢复的操作的。