多线程并发一些收藏面试精选

LockSupport原理与线程挂起/唤醒

2021-11-07  本文已影响0人  肥兔子爱豆畜子

本文一些理解和代码参考了看过的网上一些文章,感谢原作者们

之前在Java并发编程中的等待/通知范式 - 肥兔子爱豆畜子 - 博客园 (cnblogs.com)
中讨论了java并发编程里的“等待-通知”范式,里边提到了LockSupport,最近也在研究之前的一份tomcat线程dump的样本和AQS、也都涉及到这个类,所以这里有必要再深入一下。

LockSupport工具类

LockSupport打开源码一看都是static方法,典型的工具类模式,是一个在jdk里边相对偏底层的工具类,是用来编写其他稍微上层一些的并发库的工具。
作用是用来对Thread进行WAITING(TIMED_WAITING)和RUNNABLE之间的状态转换。对比一下Object.wait和notify先要synchronized锁住object -> wait阻塞等待 -> 释放锁 -> notify -> 抢锁获得锁 -> 从wait往下执行 这样的一个流程,LockSupport不需要先获取对象的锁,可以直接LockSupport.park()阻塞当前线程,LockSupport.park(object)当前线程的阻塞和object锁没有必然关系,仅是用来标识当前线程是阻塞在object上这样一种逻辑关系。

public static void park(Object blocker) {
    Thread t = Thread.currentThread();
    setBlocker(t, blocker); // UNSAFE.putObject(Thread.currentThread(), parkBlockerOffset, blocker);
    UNSAFE.park(false, 0L);
    setBlocker(t, null);
}

可以用如下例子来理解验证一下:

import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.LockSupport;

import lombok.extern.slf4j.Slf4j;

@Slf4j
public class LockSupportTest {
    private static Object lock = new Object();

    static class MyThread extends Thread {

        public MyThread(String name) {
            super(name);
        }

        @Override
        public void run() {
            //synchronized(lock) {
            //System.out.println(this.getName()+"拿到lock并开始执行run");
            log.info(this.getName() + "开始执行");
            LockSupport.park(lock);
            if (this.isInterrupted()) {
                log.info(this.getName() + "被中断");
            }
            log.info(this.getName() + "继续执行");
            //}
        }
    }

    public static void main(String[] args) throws InterruptedException {
        MyThread t1 = new MyThread("t1");
        MyThread t2 = new MyThread("t2");

        t1.start();
        TimeUnit.SECONDS.sleep(1);
        t2.start();
        TimeUnit.SECONDS.sleep(1);

        LockSupport.unpark(t2);
        TimeUnit.SECONDS.sleep(1);
        t1.interrupt();

        t1.join();
        t2.join();
    }
}

执行结果:

14:11:45.617 [t1] INFO com.lock.test.LockSupportTest - t1开始执行
14:11:46.620 [t2] INFO com.lock.test.LockSupportTest - t2开始执行
14:11:47.623 [t2] INFO com.lock.test.LockSupportTest - t2继续执行
14:11:48.626 [t1] INFO com.lock.test.LockSupportTest - t1被中断
14:11:48.626 [t1] INFO com.lock.test.LockSupportTest - t1继续执行

45秒t1开始执行,然后阻塞在LockSupport.park(lock),1秒后t2开始执行,然后也是阻塞在LockSupport.park(lock),1秒后unpark(t2),t2继续执行,再1秒后,t1.interrupt()然后t1继续执行。这是完整的一个流程。可见park(lock)跟object锁没关系。

LockSupport比较偏底层,再下面就是unsafe和native方法了,总之是通过修改内存中线程的一个布尔类型的permit字段、来标识该线程是否可被操作系统进行调度:true则代表可以被调度、false则系统不会调度该线程即挂起。后面会进一步分析permit的作用。
至于更为底层的java对象在内存中的layout以及本地线程调度的知识,这里先不表,点到为止。。。

image
LockSupport源码注释分析

在已经了解了LockSupport里边的park(), parkNanos(), unpark()这些基本方法的用法以后,其实更进一步深入可以看源代码和上边的注释,比如park()方法:

public static void park() {
    UNSAFE.park(false, 0L);
}

源代码过于简单,看下注释:

If the permit is available then it is consumed and the callreturns immediately;
otherwise the current thread becomes disabled for thread scheduling purposes and lies dormant until one of threethings happens:
•Some other thread invokes unpark with thecurrent thread as the target; or
•Some other thread interruptsthe current thread; or
•The call spuriously (that is, for no reason) returns.
This method does not report which of these caused themethod to return. Callers should re-check the conditions which causedthe thread to park in the first place. Callers may also determine,for example, the interrupt status of the thread upon return.
This method does not report which of these caused the method to return. Callers should re-check the conditions which caused the thread to park in the first place. Callers may also determine,for example, the interrupt status of the thread upon return.

如果permit是可用(permit=true),那么使用消费掉permit并返回。否则permit=false线程不被调度进入休眠,直到

  1. 被其他线程用unpark()唤醒
  2. 其他线程发送中断信号给当前线程,thread.interrupt()
  3. 调用不合逻辑的(毫无理由的)返回
    这个方法不报告上述这些返回的原因,调用者需要重新检查最先导致线程park的条件。调用者也可以在返回后确认线程的中断状态。

再看看unpark()

public static void unpark(Thread thread) {
    if (thread != null)
        UNSAFE.unpark(thread);
}

Makes available the permit for the given thread, if it was not already available.
If the thread was blocked on park then it will unblock. Otherwise, its next call to park is guaranteed not to block.
This operationis not guaranteed to have any effect at all if the giventhread has not been started.

如果当前线程的permit==false是不可用的,那么将permit变为可用,permit=true。
解除当前线程在park上的阻塞,或者如果当前线程没阻塞在park上、那么将会保证其下一次park不会被阻塞。
如果线程还没start,则不会得到上面的保证。

总结

综合上面信息,我们可以知道:

上一篇下一篇

猜你喜欢

热点阅读