简单理解下Unsafe的park和unpark的原理
我们知道各种并发框架如CountDownLatch、CyclicBarrier和Semaphore是基于AQS (AbstractQueuedSynchronizer)框架实现的,AQS框架借助于两个类:
- Unsafe(提供CAS操作) //JDK9以后引入了VarHandle变量句柄,代替了Unsafe
- LockSupport(提供park/unpark操作)
而LockSupport的park和unpark的实现是依赖于Unsafe类的prak和unpark的。重载方法中可以传入一个blocker对象,在dump线程时能获得更多的信息,用于问题排查或系统监控。
public static void park() {
U.park(false, 0L); //U = Unsafe.getUnsafe();
}
public static void park(Object blocker) {
Thread t = Thread.currentThread();
setBlocker(t, blocker);
U.park(false, 0L);
setBlocker(t, null);
}
那么Unsafe类是个什么东西呢?Unsafe的全限定名是sun.misc.Unsafe。在源码的注释中我们可以看到:执行低级、不安全操作的方法集合。从名字就知道它是不安全的,它的功能有很多,比如:volatile的读写一个变量的field,有序的写一个变量的field,直接内存操作:申请内存,释放内存,CAS的修改变量等。
本文主要说说park和unpark方法。最简单的理解:park阻塞一个线程,unpark唤醒一个线程。
核心设计原理:“许可”。park方法本质是消费许可,如果没有可消费的许可,那么就阻塞当前线程,一直等待,直到阻塞线程的unpark方法被其他线程调用,然后消费许可,当前线程被唤醒,继续执行。unpark方法本质是生产许可,一个线程刚创建出来,然后运行,此时是没有许可的,所以unpark方法可以在park方法前调用。下次park方法调用时,直接消费许可,线程不用阻塞等待许可。许可最多只有一个,连续多次调用unpark只能生产一个许可。
park方法有几种重载的形式,可以设置等待时间,等待时间可以设置为绝对的或者相对的,超过等待时间,线程会自动被唤醒。
底层实现原理:
在Linux系统下,是用的Posix线程库pthread中的mutex(互斥量),condition(条件变量)来实现的。
mutex和condition保护了一个_counter的变量,简单点说:当park时,这个变量被设置为0,当unpark时,这个变量被设置为1。
mutex和condition保护了一个_counter的变量,简单点说:当park时,这个变量被设置为0,当unpark时,这个变量被设置为1。
总结:
LockSupport的park和unpark方法相比于Synchronize的wait和notify,notifyAll方法:
1.更简单,不需要获取锁,能直接阻塞线程。
2.更直观,以thread为操作对象更符合阻塞线程的直观定义;
3.更精确,可以准确地唤醒某一个线程(notify随机唤醒一个线程,notifyAll唤醒所有等待的线程);
4.更灵活 ,unpark方法可以在park方法前调用。