基于JDK11
LockSupport是JUC中用于创建锁和其他同步类的基本线程阻塞原语。
其提供的park和unpark类似于Object类的wait和notify语义,但是前者能够针对指定线程进行阻塞和唤醒操作
我们参考啃透JAVA并发先跑一个demo
public static void main(String[] args) {
Thread t = new Thread(()->{
LockSupport.park();
System.out.println("Start working");
});
//t线程开始运行
t.start();
try {
System.out.println("Sleeping");
t.sleep(500);
} catch (InterruptedException e){
e.printStackTrace();
} finally {
//主线程运行到这里,唤醒t线程,t线程再执行打印
LockSupport.unpark(t);
}
try {
//等待t线程执行完之后,主线程再继续执行,防止主线程结束后t线程还没结束
t.join();
} catch (InterruptedException e){
e.printStackTrace();
}
}
可以看到LockSupport提供的park和unpark方法其实和object类的wait和notify方法相似,都是起到阻塞线程和唤醒线程的作用。
如果是用wait和notify实现上面的逻辑:
public static void main(String[] args) {
Object object = new Object();
Thread t = new Thread(()->{
synchronized (object){
try{
//当前线程挂起
object.wait();
} catch (InterruptedException e){
e.printStackTrace();
}finally {
System.out.println("Start working");
}
}
});
//t线程开始运行
t.start();
try{
System.out.println("Sleeping");
Thread.sleep(1000);
synchronized (object){
//唤醒持有object为Monitor的线程t
object.notifyAll();
}
} catch (InterruptedException e){
e.printStackTrace();
}
try {
//等待t线程执行完之后,主线程再继续执行,防止主线程结束后t线程还没结束
t.join();
} catch (InterruptedException e){
e.printStackTrace();
}
}
我们可以比较下两者不同:
我们了解完用法之后正式进入源码部分。注释给出一个使用park和unpark实现的FIFO Mutex的代码,可以学习一波:
public class FIFOMutex {
static {
Class<?> ensuredLoaded = LockSupport.class;
}
private final AtomicBoolean locked = new AtomicBoolean(false);
private final Queue<Thread> waitingQueue = new ConcurrentLinkedDeque<>();
public void lock(){
boolean isInterrupted = false;
//当前线程入队
waitingQueue.add(Thread.currentThread());
//加入当前线程不是队头或者设置lock失败,即上一个获得锁的线程还没解锁成功 就park阻塞线程
while(waitingQueue.peek()!=Thread.currentThread()||!locked.compareAndSet(false, true)){
LockSupport.park(this);
//要考虑中断的问题
if(Thread.interrupted()){
isInterrupted = true;
}
}
//移除队头
waitingQueue.remove();
if(isInterrupted){
//设置中断
Thread.currentThread().interrupt();
}
}
public void unlock(){
locked.set(false);
LockSupport.unpark(waitingQueue.peek());
}
}
// Hotspot implementation via intrinsics API
private static final Unsafe U = Unsafe.getUnsafe();
//获得这些字段在线程对象的偏移量
private static final long PARKBLOCKER = U.objectFieldOffset(Thread.class, "parkBlocker");
private static final long SECONDARY = U.objectFieldOffset(Thread.class, "threadLocalRandomSecondarySeed");
private static final long TID = U.objectFieldOffset(Thread.class, "tid");
构造函数是私有的
private LockSupport() {
} // Cannot be instantiated.
/*
* 发给目标线程一个许可证,该许可证被park消费,用于唤醒被park阻塞的线程
*
* 该许可证可以提前发给线程备用,也可以等线程陷入阻塞后,在等待许可证时再(由另一个线程)给它,进而唤醒线程。
* 连续重复发给线程的许可证只被视为一个许可证。
*/
public static void unpark(Thread thread) {
if(thread != null) {
U.unpark(thread);
}
}
具体是UnSafe类的unpark本地方法实现的,在C++中加锁解锁分别是用的是pthread_mutex_lock
和pthread_mutex_unlock
,唤醒线程则是pthread_cond_signal
该线程在下列情况发生之前都会被阻塞:① 调用unpark函数,释放该线程的许可。② 该线程被中断。③ 设置的时间到了
该操作对中断标记位为中断的线程无效
有两种重载的方法:
// 作用同park(blocker)方法,只是不设置阻塞标记
public static void park() {
U.park(false, 0L);
}
这种方法不会设置阻塞标记
/*
* 等待消费一个许可证,这会使线程陷入阻塞。blocker参数仅作为线程阻塞标记。
*
* 如果提前给过许可,则线程继续执行。
* 如果陷入阻塞后等待许可,则可由别的线程发给它许可(在别的线程中调用unpark)。
* 使用线程中断也可以唤醒陷入阻塞的线程。
*
* 注:对标记为中断的线程使用阻塞无效
*/
public static void park(Object blocker) {
Thread t = Thread.currentThread();
// 标记线程陷入了阻塞
setBlocker(t, blocker);
// 使线程一直陷入阻塞,直到被唤醒
U.park(false, 0L);
// 标记线程脱离了阻塞
setBlocker(t, null);
}
我们来看看这个设置阻塞标记的方法,这个方法的作用在于方便调试,方便看到线程在哪个对象阻塞了。
// 获取线程的阻塞标记
public static Object getBlocker(Thread t) {
if(t == null) {
throw new NullPointerException();
}
return U.getObjectVolatile(t, PARKBLOCKER);
}
// 设置线程t的parkBlocker字段为arg,用来标记该线程陷入了阻塞
private static void setBlocker(Thread t, Object arg) {
// Even though volatile, hotspot doesn't need a write barrier here.
U.putObject(t, PARKBLOCKER, arg);
}
park具体也是UnSafe类的park本地方法实现的,核心就是C++有一个int变量_count,如果_count>0则可以通行,不阻塞。即park方法会直接返回,另外park方法返回后,_counter会被赋值为0,unpark方法可以将_counter置为1,并且唤醒当前等待的线程。所以本质上park,unpark底层用的是二元信号量来实现。所以:
如果在park()之前执行了unpark()会怎样?
线程不会被阻塞,直接跳过park(),继续执行后续内容
一些面试问题总结:
不会,释放锁是在condition.await()释放,但是await方法又是依赖park来实现阻塞线程的。
park,wait语义都是线程间通信,而sleep的语义只是让线程失去CPU时间片,并不会释放锁。而wait会释放掉锁。但是sleep与wait,park都是进入线程的WAITING状态.我理解WAITING状态并不是自旋,因为自旋其实是还是占有CPU时间片的。可以理解为线程进入了等待队列,
https://www.cnblogs.com/tong-yuan/p/11768904.html