一. LockSupport类介绍
前面分析中,阻塞和唤醒线程都会使用到LockSupport工具来完成相应工作,LockSupport定义了一组公共静态方法,这些方法提供了最基本的线程阻塞和唤醒公共,而LockSupport也成为构建同步组件的基础工具。
LockSupport定义了一组以park开头的方法用来阻塞当前线程,以及upark方法用来唤醒线程。这些方法如下所示:
方法名称 | 描述 |
---|---|
void park() | 阻塞当前线程,如果调用unpark(Thread)或者当前线程被中断,才能从park()方法返回 |
void parkNanos(long nanos) | 阻塞当前线程,最长不超过nanos纳秒,返回条件在park()的基础上增加了超时返回 |
void parkUntil(long deadline) | 阻塞当前线程,直到deadline |
void unpark(Thread thread) | 唤醒处于阻塞的线程thread |
三种形式的 park 还各自支持一个 blocker 对象参数。此对象在线程受阻塞时被记录,以允许监视工具和诊断工具确定线程受阻塞的原因。(这样的工具可以使用方法 getBlocker(java.lang.Thread) 访问 blocker。)建议最好使用这些形式,而不是不带此参数的原始形式。在锁实现中提供的作为 blocker 的普通参数是 this。
看下线程dump的结果来理解blocker的作用。
从线程dump结果可以看出:
有blocker的可以传递给开发人员更多的现场信息,通过jstack命令可以非常方便的监控具体的阻塞对象,方便定位问题。所以java6新增加带blocker入参的系列park方法,替代原有的park方法。
Unsafe的park和unpark
LockSupport类是Java6(JSR166-JUC)引入的一个类,提供了基本的线程同步原语。LockSupport实际上是调用了Unsafe类里的函数,归结到Unsafe里,只有两个函数:
/**
* 为指定线程提供“许可(permit)”
*/
public native void unpark(Thread jthread);
/**
* 阻塞指定时间等待“许可”。
* @param isAbsolute: 时间是绝对的,还是相对的
* @param time:等待许可的时间
*/
public native void park(boolean isAbsolute, long time);
上面的这个“许可”是不能叠加的,“许可”是一次性的。
比如线程B连续调用了三次unpark函数,当线程A调用park函数就使用掉这个“许可”,如果线程A再次调用park,则进入等待状态。
注意,unpark函数可以先于park调用。比如线程B调用unpark函数,给线程A发了一个“许可”,那么当线程A调用park时,它发现已经有“许可”了,那么它会马上再继续运行。
可能有些朋友还是不理解“许可”这个概念,我们深入HotSpot的源码来看看。
每个java线程都有一个Parker实例,Parker类是这样定义的:
class Parker : public os::PlatformParker {
private:
volatile int _counter ;
...
public:
void park(bool isAbsolute, jlong time);
void unpark();
...
}
class PlatformParker : public CHeapObj<mtInternal> {
protected:
pthread_mutex_t _mutex [1] ;
pthread_cond_t _cond [1] ;
...
}
可以看到Parker类实际上用Posix的mutex,condition来实现的。在Parker类里的_counter字段,就是用来记录所谓的“许可”的。
当调用park时,先尝试直接能否直接拿到“许可”,即_counter>0
时,如果成功,则把_counter
设置为0,并返回: