这是我参与8月更文挑战的第7天,活动详情查看:8月更文挑战
lock接口
lock只是一个接口,在JUC下的一个子包Locks包里。Lock需要显式地获取和释放锁,虽然不如隐式获取锁的便捷,但有了锁获取与释放的可操作性、可中断的获取锁及超时获取锁等同步特性。代码如下:
public interface Lock {
void lock();
void lockInterruptibly() throws InterruptedException;
boolean tryLock();
boolean tryLock(long time, TimeUnit unit) throws InterruptedException;
void unlock();
Condition newCondition();
}
void lock():获取锁,若锁不可用,则当前线程将会阻塞,直到获得锁;
void lockInterruptibly() throws InterruptedException:获取锁,当被中断或获取到锁才返回,若锁不可用,则当前线程被阻塞,直到获取锁或被中断;
boolean tryLock():尝试获取锁,并立即返回;true:获取锁成功;false:获取锁失败;
boolean tryLock(long time, TimeUnit unit) throws InterruptedException:尝试在指定的超时时间获取锁,当获取到锁时返回true;当超时或被中断时返回false;
Condition newCondition():返回一个和锁绑定的条件队列;在等待条件之前线程必须先获取当前锁,同时await()方法会原子地释放锁,并在返回之前重新获取到锁;
ReentrantLock和ReadWriteLock是此接口的实现;
void unlock():释放锁;
lockSupport工具类
LockSupport包含一组的公共静态方法,这些方法提供了基础的线程阻塞和唤醒功能,而LockSupport也成为构建同步组件的基础工具。下面看源码
public class LockSupport {
private LockSupport() {} // Cannot be instantiated.
//设置阻塞当前线程的操作者,一般保存阻塞操作的线程Thread对象
//parkBlocker对象为线程Thread.class的属性parkBlocker
private static void setBlocker(Thread t, Object arg) {
// Even though volatile, hotspot doesn't need a write barrier here.
UNSAFE.putObject(t, parkBlockerOffset, arg);
}
public static void unpark(Thread thread) {
if (thread != null)
UNSAFE.unpark(thread);
}
public static void park(Object blocker) {
Thread t = Thread.currentThread();
setBlocker(t, blocker);
UNSAFE.park(false, 0L);
setBlocker(t, null);
}
//功能与park基本相同,此方法添加阻塞时间参数
public static void parkNanos(Object blocker, long nanos) {
if (nanos > 0) {
Thread t = Thread.currentThread();
setBlocker(t, blocker);
UNSAFE.park(false, nanos);
setBlocker(t, null);
}
}
//功能与park基本相同,此方法添加阻塞到某个时间点的参数
public static void parkUntil(Object blocker, long deadline) {
Thread t = Thread.currentThread();
setBlocker(t, blocker);
UNSAFE.park(true, deadline);
setBlocker(t, null);
}
//返回给定线程的parkBlocker参数
public static Object getBlocker(Thread t) {
if (t == null)
throw new NullPointerException();
return UNSAFE.getObjectVolatile(t, parkBlockerOffset);
}
//
public static void park() {
UNSAFE.park(false, 0L);
}
public static void parkNanos(long nanos) {
if (nanos > 0)
UNSAFE.park(false, nanos);
}
public static void parkUntil(long deadline) {
UNSAFE.park(true, deadline);
}
//unsafe对象
private static final sun.misc.Unsafe UNSAFE;
//保存parkBlocker属性在Thread中的偏移量
// 表示内存偏移地址
private static final long parkBlockerOffset;
// 表示内存偏移地址
private static final long SEED;
// 表示内存偏移地址
private static final long PROBE;
// 表示内存偏移地址
private static final long SECONDARY;
static {
try {
// 获取Unsafe实例
UNSAFE = sun.misc.Unsafe.getUnsafe();
// 线程类类型
Class<?> tk = Thread.class;
// 获取Thread的parkBlocker字段的内存偏移地址
parkBlockerOffset = UNSAFE.objectFieldOffset
(tk.getDeclaredField("parkBlocker"));
// 获取Thread的threadLocalRandomSeed字段的内存偏移地址
SEED = UNSAFE.objectFieldOffset
(tk.getDeclaredField("threadLocalRandomSeed"));
// 获取Thread的threadLocalRandomProbe字段的内存偏移地址
PROBE = UNSAFE.objectFieldOffset
(tk.getDeclaredField("threadLocalRandomProbe"));
// 获取Thread的threadLocalRandomSecondarySeed字段的内存偏移地址
SECONDARY = UNSAFE.objectFieldOffset
(tk.getDeclaredField("threadLocalRandomSecondarySeed"));
} catch (Exception ex) { throw new Error(ex); }
}
}
LockSupport的核心函数都是基于Unsafe类中定义的park和unpark函数,对两个函数的说明如下: park函数,阻塞线程,并且该线程在下列情况发生之前都会被阻塞: 调用unpark函数,释放该线程的许可。该线程被中断。设置的时间到了。并且,当time为绝对时间时,isAbsolute为true,否则,isAbsolute为false。当time为0时,表示无限等待,直到unpark发生。 unpark函数,释放线程的许可,即激活调用park后阻塞的线程。这个函数不是安全的,调用这个函数时要确保线程依旧存活。
park函数
调用park函数时,首先获取当前线程,然后设置当前线程的parkBlocker字段,即调用setBlocker函数,之后调用Unsafe类的park函数,之后再调用setBlocker函数。同时park函数中要调用两次setBlocker函数, 因为调用park函数时,当前线程首先设置好parkBlocker字段,然后再调用Unsafe的park函数,此后,当前线程就已经阻塞了,等待该线程的unpark函数被调用,所以后面的一个setBlocker函数无法运行,unpark函数被调用,该线程获得许可后,就可以继续运行了,也就运行第二个setBlocker,把该线程的parkBlocker字段设置为null,这样就完成了整个park函数的逻辑。如果没有第二个setBlocker,那么之后没有调用park(Object blocker),而直接调用getBlocker函数,得到的还是前一个park(Object blocker)设置的blocker,显然是不符合逻辑的。总之,必须要保证在park(Object blocker)整个函数执行完后,该线程的parkBlocker字段又恢复为null。所以,park(Object)型函数里必须要调用setBlocker函数两次。
unpark函数
此函数表示如果给定线程的许可尚不可用,则使其可用。如果线程在 park 上受阻塞,则它将解除其阻塞状态。否则,保证下一次调用 park 不会受阻塞。如果给定线程尚未启动,则无法保证此操作有任何效果。若给定线程正常运行,则本次unpark会保证下次针对该线程的park不会阻塞该线程,当线程还未启动,umpark操作不会有任何作用。
总结就是:许可(permit)的上限是1。 park: 没有许可的时候,permit 为 0 ,调用 park 会阻塞;有许可的时候,permit 为 1 , 调用 park 会扣除一个许可,然后返回。 unpark:没有许可的时候,permit 为 0 ,调用 unpark 会增加一个许可,因为许可上限是 1 , 所以调用多次也只会为 1 个。 线程初始的时候是没有许可的。 park 的当前线程如果被中断,会立即返回,并不会抛出中断异常。
写在最后
LockSupport的Park及Unpark是通过Unsafe的对应类实现的,而Unsafe的相关实现在jvm中,实现park及unpark的关键是Mutex互斥体、Condition信号量、_counter计数器,这点如果感兴趣可以自行查阅相关源码。