手记

Java并发包中的LockSupport工具类

一、LockSupport工具类简介

   LockSupport类是一个工具类,它位于JDK中的rt.jar包里面,它的主要作用是对线程进行挂起和唤醒,它是创建锁和其他同步类的基础。

   

   LockSupport类与每个使用它的线程都会关联一个许可证,在默认情况下调用LockSupport类的方法的线程是不持有许可证的。

   

   LockSupport类是使用Unsafe类实现的。

   

二、阅读LockSupport类的源码

LockSupport类中含义几个主要的方法,具体如下:

1. void park()方法:  

如果调用LockSupport.park()方法的线程已经拿到了与LockSupport关联的许可证,则调用时会马上返回,否则调用线程会被禁止参与线程的调度,也就是会被阻塞挂起。

pack()函数的源码如下:

public static void park() {
        //挂起线程
        UNSAFE.park(false, 0L);
}


在其他线程调用LockSupport.unpark(Thread thread)方法并且将当前线程作为参数时,调用LockSupport.park()方法而被阻塞的线程会返回。

另外,如果其他线程调用了阻塞线程的interrupt()方法(中断线程),设置了中断标志或者线程被虚假唤醒,则阻塞线程也会返回。所以在调用LockSupport.park()方法时最好使用循环条件判断。             

注意:因为调用LockSupport.park()方法而被阻塞的线程被其他线程中断而返回时,并不会抛出InterruptedException异常。

park()方法返回时不会告诉你因何种原因返回,所以调用者需要根据之前调用park方法的原因,再次检查条件是否满足,如果不满足则还需要再次调用park()方法

示例代码:

import java.util.concurrent.locks.LockSupport;

/**
 * @ClassName: LockSupportDemo
 * @Description:  LockSupport工具类: 它的主要作用是对线程进行挂起和唤醒,它是创建锁和其他同步类的基础。
 * 如果调用LockSupport.park()方法的线程已经拿到了与LockSupport关联的许可证,则调用时会马上返回,
 * 否则调用线程会被禁止参与线程的调度,也就是会被阻塞挂起。
 * @Author: liuhefei
 * @blog: https://www.imooc.com/u/1323320/articles
 **/
public class LockSupportDemo {

    public static void main(String[] args) {
        System.out.println("start...");
        //直接在main函数中调用park()方法最终只会输出start...,然后当前线程被挂起。
        //在默认情况下,调用线程是不持有许可证的。
        LockSupport.park();
        System.out.println("end...");
    }
}


pack(Object blocker)方法的源码如下:

public static void park(Object blocker) {
     //获取调用线程
     Thread t = Thread.currentThread();
     //设置该线程的blocker变量
     setBlocker(t, blocker);
     //挂起线程
     UNSAFE.park(false, 0L);
     //线程被激活后清除blocker变量,因为一般都是在线程阻塞时才会分析原因
     setBlocker(t, null);
}

Thread类中有个变量volatile Object parkBlocker,用来存放park方法传递的blocker对象,也就是把blocker变量存放到了调用park方法的线程的成员变量里面。


当线程在没有持有许可证的情况下,调用LockSupport.park(blocker)方法而被阻塞挂起时,这个blocker对象会被记录到该线程的内部。

使用线程诊断工具可以观察线程被阻塞的原因,诊断工具是通过调用getBlocker(Thread)方法来获取blocker对象的,所以JDK推荐我们使用带有blocker参数的park方法,并且blocker被设置为this,这样当打印线程堆栈排插问题时就能知道是哪个类被阻塞了。


2. void unpark(Thread thread)方法:    

当一个线程调用LockSupport.unpark()方法时,如果参数thread线程没有持有thread与LockSupport类关联的许可证,则让thread线程持有;

而如果thread之前因调用LockSupport.park()方法时被挂起,则调用LockSupport.unpark()方法后,该线程会被唤醒。

而如果thread之前没有调用LockSupport.park()方法,则调用LockSupport.unpark()方法后,再调用LockSupport.park()方法,其会立刻返回。

unpark()方法的源码如下:

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

示例代码1:

import java.util.concurrent.locks.LockSupport;

/**
 * @ClassName: LockSupportDemo1
 * @Description: LockSupport工具类
 * @Author: liuhefei
 * @blog: https://www.imooc.com/u/1323320/articles
 **/
public class LockSupportDemo1 {

    public static void main(String[] args) {
        System.out.println("begin park!");

        //使用当前线程获取到许可证
        LockSupport.unpark(Thread.currentThread());

        //再次调用LockSupport.park()方法
        LockSupport.park();

        System.out.println("end park!");  //立马返回
    }
}

示例代码2:

import java.util.concurrent.locks.LockSupport;

/**
 * @ClassName: LockSupportDemo2
 * @Description: LockSupport工具类
 * @Author: liuhefei
 * @blog: https://www.imooc.com/u/1323320/articles
 **/
public class LockSupportDemo2 {

    public static void main(String[] args) throws InterruptedException{
        Thread threadOne = new Thread(new Runnable() {
            @Override
            public void run() {
                System.out.println("子线程开始...");
                //调用LockSupport.park()方法,挂起自己
                //默认情况下,子线程没有持有许可证,因而它会把自己挂起
                LockSupport.park();
                System.out.println("子线程结束...");
            }
        });

        //启动子线程
        threadOne.start();

        //主线程休眠2秒
        Thread.sleep(2000);

        System.out.println("主线程运行...");

        //调用LockSupport.unpark()方法让threadOne线程持有许可证,然后LockSupport.park()方法返回
        LockSupport.unpark(threadOne);

        //park()方法返回时不会告诉你因何种原因返回,所以调用者需要根据之前调用park方法的原因,再次检查条件是否满足,如果不满足则还需要再次调用park()方法

    }
}


3. void parkNanos(long nanos)方法:    

该方法的作用与park方法相似,如果调用LockSupport.park()方法的线程已经拿到了与LockSupport关联的许可证,则调用LockSupport.parkNanos(long nanos)方法后会马上返回。

该方法与park()方法的不同之处在于,如果没有拿到许可证,则调用线程会被挂起nanos时间后修改为自动返回。

parkNanos(long nanos)方法的源码如下:

public static void parkNanos(long nanos) {
    if (nanos > 0)
        UNSAFE.park(false, nanos);
}


4. void parkNanos(Object blocker, long nanos)方法:

与park(Object blocker)方法相比,只是多了一个超时时间,其源码如下:

public static void parkNanos(Object blocker, long nanos) {
       if (nanos > 0) {
           // 获取调用线程
           Thread t = Thread.currentThread();
           //设置该线程的blocker变量
           setBlocker(t, blocker);
           UNSAFE.park(false, nanos);
           setBlocker(t, null);
       }
}


5. void parkUntil(Object blocker, long deadline)方法,其源码如下:

public static void parkUntil(Object blocker, long deadline) {
        //获取调用线程
       Thread t = Thread.currentThread();
       //设置该线程的blocker变量
       setBlocker(t, blocker);
       UNSAFE.park(true, deadline); //isAbsolute=true,time=deadline 表示到deadline时间后返回
       setBlocker(t, null);
}

其中参数deadline的时间单位为ms,该时间是从1970年到现在某一个时间点的毫秒值。

该方法与void parkNanos(Object blocker, long nanos)方法的区别是:

void parkNanos(Object blocker, long nanos)方法是从当前计算等待nanos秒时间;

void parkUntil(Object blocker, long deadline)方法是指定一个时间点,比如需要等到2019.12.22日12:00:00,则把这个时间点转换为从1970年到这个时间的总毫秒数。


示例代码:

import java.util.concurrent.locks.LockSupport;

/**
 * @ClassName: LockSupportDemo3
 * @Description: LockSupport工具类
 *  调用park()方法后的线程被中断后会返回
 *  只有中断子线程,子线程才会运行结束,如果子线程不被中断,即使你调用unpark(thread)方法子线程也不会结束
 * @Author: liuhefei
 * @blog: https://www.imooc.com/u/1323320/articles
 **/
public class LockSupportDemo3 {

    public static void main(String[] args) throws InterruptedException{
        Thread threadOne = new Thread(new Runnable() {
            @Override
            public void run() {
                System.out.println("子线程开始执行...");
                //默认情况下,子线程不持有许可证, 调用park方法挂起自己,只有被中断才会退出循环
                while (!Thread.currentThread().isInterrupted()){
                    LockSupport.park();
                }
                System.out.println("子线程结束...");
            }
        });

        //启动子线程
        threadOne.start();

        //主线程休眠2秒
        Thread.sleep(2000);

        System.out.println("主线程执行....");

        //中断子线程
        threadOne.interrupt();
    }


}

参考:《Java并发编程之美》

请诸君多多支持!


0人推荐
随时随地看视频
慕课网APP