LockSupport深度解析:线程阻塞与唤醒的底层实现原理
<h2 id="locksupport简介">LockSupport简介</h2><p>LockSupprot 用来阻塞和唤醒线程,底层实现依赖于 Unsafe 类。</p>
<p>LockSupport用来创建锁和其他同步类的基本线程阻塞原语。简而言之,当调用LockSupport.park时,表示当前线程将会等待,直至获得许可,当调用LockSupport.unpark时,必须把等待获得许可的线程作为参数进行传递,好让此线程继续运行。在AQS中大量使用,AQS最终都是使用LockSupport来阻塞线程的。</p>
<p>该类包含一组用于阻塞和唤醒线程的静态方法,这些方法主要是围绕 park 和 unpark 展开,话不多说,直接来看一个简单的例子吧。</p>
<pre><code class="language-java">public class LockSupportDemo1 {
public static void main(String[] args) {
Thread mainThread = Thread.currentThread();
// 创建一个线程从1数到1000
Thread counterThread = new Thread(() -> {
for (int i = 1; i <= 1000; i++) {
System.out.println(i);
if (i == 500) {
// 当数到500时,唤醒主线程
LockSupport.unpark(mainThread);
}
}
});
counterThread.start();
// 主线程调用park
LockSupport.park();
System.out.println("Main thread was unparked.");
}
}
</code></pre>
<p>上面的代码中,当 counterThread 数到 500 时,它会唤醒 mainThread。而 mainThread 在调用 park 方法时会被阻塞,直到被 unpark。</p>
<p>LockSupport 中的方法不多,这里将这些方法做一个总结:</p>
<h3 id="阻塞线程">阻塞线程</h3>
<ol>
<li><code>void park()</code>:阻塞当前线程,如果调用 unpark 方法或线程被中断,则该线程将变得可运行。请注意,park 不会抛出 InterruptedException,因此线程必须单独检查其中断状态。</li>
<li><code>void park(Object blocker)</code>:功能同方法 1,入参增加一个 Object 对象,用来记录导致线程阻塞的对象,方便问题排查。</li>
<li><code>void parkNanos(long nanos)</code>:阻塞当前线程一定的纳秒时间,或直到被 unpark 调用,或线程被中断。</li>
<li><code>void parkNanos(Object blocker, long nanos)</code>:功能同方法 3,入参增加一个 Object 对象,用来记录导致线程阻塞的对象,方便问题排查。</li>
<li><code>void parkUntil(long deadline)</code>:阻塞当前线程直到某个指定的截止时间(以毫秒为单位),或直到被 unpark 调用,或线程被中断。</li>
<li><code>void parkUntil(Object blocker, long deadline)</code>:功能同方法 5,入参增加一个 Object 对象,用来记录导致线程阻塞的对象,方便问题排查。</li>
</ol>
<h3 id="唤醒线程">唤醒线程</h3>
<p><code>void unpark(Thread thread)</code>:唤醒一个由 park 方法阻塞的线程。如果该线程未被阻塞,那么下一次调用 park 时将立即返回。这允许“先发制人”式的唤醒机制。</p>
<p>实际上,LockSupport 阻塞和唤醒线程的功能依赖于 <code>sun.misc.Unsafe</code>,这是一个很底层的类,比如 LockSupport 的 park 方法是通过 <code>unsafe.park()</code> 方法实现的。</p>
<h2 id="locksupport源码分析">LockSupport源码分析</h2>
<h3 id="类的属性">类的属性</h3>
<pre><code class="language-java">public class LockSupport {
// Hotspot implementation via intrinsics API
private static final sun.misc.Unsafe UNSAFE;
// 表示内存偏移地址
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); }
}
}
</code></pre>
<p>说明: UNSAFE字段表示sun.misc.Unsafe类,一般程序中不允许直接调用,而long型的表示实例对象相应字段在内存中的偏移地址,可以通过该偏移地址获取或者设置该字段的值。</p>
<h3 id="类的构造函数">类的构造函数</h3>
<pre><code class="language-java">// 私有构造函数,无法被实例化
private LockSupport() {}
</code></pre>
<p>说明: LockSupport只有一个私有构造函数,无法被实例化。</p>
<h3 id="核心函数分析">核心函数分析</h3>
<p>在分析LockSupport函数之前,先引入sun.misc.Unsafe类中的park和unpark函数,因为LockSupport的核心函数都是基于Unsafe类中定义的park和unpark函数,下面给出两个函数的定义:</p>
<pre><code class="language-java">public native void park(boolean isAbsolute, long time);
public native void unpark(Thread thread);
</code></pre>
<p>说明: 对两个函数的说明如下:</p>
<ul>
<li>
<p>park函数,阻塞线程,并且该线程在下列情况发生之前都会被阻塞:</p>
<ul>
<li>
<p>调用unpark函数,释放该线程的许可。</p>
</li>
<li>
<p>该线程被中断。</p>
</li>
<li>
<p>设置的时间到了。并且,当time为绝对时间时,isAbsolute为true,否则,isAbsolute为false。当time为0时,表示无限等待,直到unpark发生。</p>
</li>
</ul>
</li>
<li>
<p>unpark函数,释放线程的许可,即激活调用park后阻塞的线程。这个函数不是安全的,调用这个函数时要确保线程依旧存活。</p>
</li>
</ul>
<h4 id="park函数">park函数</h4>
<p>park函数有两个重载版本,方法摘要如下</p>
<pre><code class="language-java">public static void park();
public static void park(Object blocker);
</code></pre>
<p>说明: 两个函数的区别在于park()函数没有没有blocker,即没有设置线程的parkBlocker字段。park(Object)型函数如下。</p>
<pre><code class="language-java">public static void park(Object blocker) {
// 获取当前线程
Thread t = Thread.currentThread();
// 设置Blocker
setBlocker(t, blocker);
// 获取许可
UNSAFE.park(false, 0L);
// 重新可运行后再此设置Blocker
setBlocker(t, null);
}
</code></pre>
<p>说明: 调用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函数两次。setBlocker方法如下。</p>
<pre><code class="language-java">private static void setBlocker(Thread t, Object arg) {
// 设置线程t的parkBlocker字段的值为arg
UNSAFE.putObject(t, parkBlockerOffset, arg);
}
</code></pre>
<p>说明: 此方法用于设置线程t的parkBlocker字段的值为arg。</p>
<p>另外一个无参重载版本,park()函数如下。</p>
<pre><code class="language-java">public static void park() {
// 获取许可,设置时间为无限长,直到可以获取许可
UNSAFE.park(false, 0L);
}
</code></pre>
<p>说明: 调用了park函数后,会禁用当前线程,除非许可可用。在以下三种情况之一发生之前,当前线程都将处于休眠状态,即下列情况发生时,当前线程会获取许可,可以继续运行。</p>
<ul>
<li>
<p>其他某个线程将当前线程作为目标调用 unpark。</p>
</li>
<li>
<p>其他某个线程中断当前线程。</p>
</li>
<li>
<p>该调用不合逻辑地(即毫无理由地)返回。</p>
</li>
</ul>
<h4 id="parknanos函数">parkNanos函数</h4>
<p>此函数表示在许可可用前禁用当前线程,并最多等待指定的等待时间。具体函数如下。</p>
<pre><code class="language-java">public static void parkNanos(Object blocker, long nanos) {
if (nanos > 0) { // 时间大于0
// 获取当前线程
Thread t = Thread.currentThread();
// 设置Blocker
setBlocker(t, blocker);
// 获取许可,并设置了时间
UNSAFE.park(false, nanos);
// 设置许可
setBlocker(t, null);
}
}
</code></pre>
<p>说明: 该函数也是调用了两次setBlocker函数,nanos参数表示相对时间,表示等待多长时间。</p>
<h4 id="parkuntil函数">parkUntil函数</h4>
<p>此函数表示在指定的时限前禁用当前线程,除非许可可用, 具体函数如下:</p>
<pre><code class="language-java">public static void parkUntil(Object blocker, long deadline) {
// 获取当前线程
Thread t = Thread.currentThread();
// 设置Blocker
setBlocker(t, blocker);
UNSAFE.park(true, deadline);
// 设置Blocker为null
setBlocker(t, null);
}
</code></pre>
<p>说明: 该函数也调用了两次setBlocker函数,deadline参数表示绝对时间,表示指定的时间。</p>
<h4 id="unpark函数">unpark函数</h4>
<p>此函数表示如果给定线程的许可尚不可用,则使其可用。如果线程在 park 上受阻塞,则它将解除其阻塞状态。否则,保证下一次调用 park 不会受阻塞。如果给定线程尚未启动,则无法保证此操作有任何效果。具体函数如下:</p>
<pre><code class="language-java">public static void unpark(Thread thread) {
if (thread != null) // 线程为不空
UNSAFE.unpark(thread); // 释放该线程许可
}
</code></pre>
<p>说明: 释放许可,指定线程可以继续运行。</p>
<h2 id="更深入的理解">更深入的理解</h2>
<h3 id="与-synchronzed-的区别">与 synchronzed 的区别</h3>
<p>synchronzed 会使线程阻塞,线程会进入 BLOCKED 状态,而调用 LockSupprt 方法阻塞线程会使线程进入到 WAITING 状态。</p>
<h3 id="threadsleep和objectwait的区别">Thread.sleep()和Object.wait()的区别</h3>
<p>首先,我们先来看看Thread.sleep()和Object.wait()的区别,这是一个烂大街的题目了,大家应该都能说上来两点。</p>
<ul>
<li>
<p>Thread.sleep()不会释放占有的锁,Object.wait()会释放占有的锁;</p>
</li>
<li>
<p>Thread.sleep()必须传入时间,Object.wait()可传可不传,不传表示一直阻塞下去;</p>
</li>
<li>
<p>Thread.sleep()到时间了会自动唤醒,然后继续执行;</p>
</li>
<li>
<p>Object.wait()不带时间的,需要另一个线程使用Object.notify()唤醒;</p>
</li>
<li>
<p>Object.wait()带时间的,假如没有被notify,到时间了会自动唤醒,这时又分好两种情况,一是立即获取到了锁,线程自然会继续执行;二是没有立即获取锁,线程进入同步队列等待获取锁;</p>
</li>
</ul>
<p>其实,他们俩最大的区别就是Thread.sleep()不会释放锁资源,Object.wait()会释放锁资源。</p>
<h3 id="objectwait和conditionawait的区别">Object.wait()和Condition.await()的区别</h3>
<p>Object.wait()和Condition.await()的原理是基本一致的,不同的是Condition.await()底层是调用LockSupport.park()来实现阻塞当前线程的。</p>
<p>实际上,它在阻塞当前线程之前还干了两件事,一是把当前线程添加到条件队列中,二是“完全”释放锁,也就是让state状态变量变为0,然后才是调用LockSupport.park()阻塞当前线程。</p>
<h3 id="threadsleep和locksupportpark的区别">Thread.sleep()和LockSupport.park()的区别</h3>
<p>LockSupport.park()还有几个兄弟方法——parkNanos()、parkUtil()等,我们这里说的park()方法统称这一类方法。</p>
<ul>
<li>
<p>从功能上来说,Thread.sleep()和LockSupport.park()方法类似,都是阻塞当前线程的执行,且都不会释放当前线程占有的锁资源;</p>
</li>
<li>
<p>Thread.sleep()没法从外部唤醒,只能自己醒过来;</p>
</li>
<li>
<p>LockSupport.park()方法可以被另一个线程调用LockSupport.unpark()方法唤醒;</p>
</li>
<li>
<p>Thread.sleep()方法声明上抛出了InterruptedException中断异常,所以调用者需要捕获这个异常或者再抛出;</p>
</li>
<li>
<p>LockSupport.park()方法不需要捕获中断异常;</p>
</li>
<li>
<p>Thread.sleep()本身就是一个native方法;</p>
</li>
<li>
<p>LockSupport.park()底层是调用的Unsafe的native方法;</p>
</li>
</ul>
<h3 id="objectwait和locksupportpark的区别">Object.wait()和LockSupport.park()的区别</h3>
<p>二者都会阻塞当前线程的运行,他们有什么区别呢? 经过上面的分析相信你一定很清楚了,真的吗? 往下看!</p>
<ul>
<li>
<p>Object.wait()方法需要在synchronized块中执行;</p>
</li>
<li>
<p>LockSupport.park()可以在任意地方执行;</p>
</li>
<li>
<p>Object.wait()方法声明抛出了中断异常,调用者需要捕获或者再抛出;</p>
</li>
<li>
<p>LockSupport.park()不需要捕获中断异常;</p>
</li>
<li>
<p>Object.wait()不带超时的,需要另一个线程执行notify()来唤醒,但不一定继续执行后续内容;</p>
</li>
<li>
<p>LockSupport.park()不带超时的,需要另一个线程执行unpark()来唤醒,一定会继续执行后续内容;</p>
</li>
</ul>
<p>park()/unpark()底层的原理是“二元信号量”,你可以把它相像成只有一个许可证的Semaphore,只不过这个信号量在重复执行unpark()的时候也不会再增加许可证,最多只有一个许可证。</p>
<h3 id="如果在wait之前执行了notify会怎样">如果在wait()之前执行了notify()会怎样?</h3>
<p>如果当前的线程不是此对象锁的所有者,却调用该对象的notify()或wait()方法时抛出IllegalMonitorStateException异常;</p>
<p>如果当前线程是此对象锁的所有者,wait()将一直阻塞,因为后续将没有其它notify()唤醒它。</p>
<h3 id="如果在park之前执行了unpark会怎样">如果在park()之前执行了unpark()会怎样?</h3>
<p>线程不会被阻塞,直接跳过park(),继续执行后续内容</p>
<h3 id="locksupportpark会释放锁资源吗">LockSupport.park()会释放锁资源吗?</h3>
<p>不会,它只负责阻塞当前线程,释放锁资源实际上是在Condition的await()方法中实现的。</p>
</div>
<div id="MySignature" role="contentinfo">
<p>本文来自在线网站:seven的菜鸟成长之路,作者:seven,转载请注明原文链接:www.seven97.top</p><br><br>
来源:https://www.cnblogs.com/sevencoding/p/19588531
頁:
[1]