liuxiaoshui
发布于 2023-11-25 / 19 阅读
0
0

ReentraintLock中park()和unpark()

关于park()阻塞线程的唤醒方式

Park阻塞线程唤醒有两种方式:

1、中断

2、unlock->release()->unpark()

中断唤醒:

interrupt()存在的意义

早期停止线程的方式是Thread里有个native的方法stop0(),相当于linux里的kill -9,会产生各种问题,因此,Java提供了一种用于停止线程的机制——中断。

上代码演示效果

public static void main(String[] args) {

        Thread t0 = new Thread(new Runnable() {
            @Override
            public void run() {
                Thread current = Thread.currentThread();
                log.info("{},开始执行!", current.getName());
                for (; ; ) {//spin 自旋
                    log.info("准备park住当前线程:{}....", current.getName());
                    LockSupport.park();

                    log.info("当前线程{}已经被唤醒....", current.getName());
                }
            }
        }, "t0");

        t0.start();

        try {
            Thread.sleep(2000);
            log.info("准备唤醒{}线程!", t0.getName());
            LockSupport.unpark(t0);
            Thread.sleep(10000);
            t0.interrupt();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }

执行结果:

10:42:04.711 [t0] INFO com.yg.edu.lock.Juc01_Thread_LockSupport - t0,开始执行!
10:42:04.715 [t0] INFO com.yg.edu.lock.Juc01_Thread_LockSupport - 准备park住当前线程:t0....
10:42:06.722 [main] INFO com.yg.edu.lock.Juc01_Thread_LockSupport - 准备唤醒t0线程!
10:42:06.723 [t0] INFO com.yg.edu.lock.Juc01_Thread_LockSupport - 当前线程t0已经被唤醒....
10:42:06.723 [t0] INFO com.yg.edu.lock.Juc01_Thread_LockSupport - 准备park住当前线程:t0....
10:43:07.722 [t0] INFO com.yg.edu.lock.Juc01_Thread_LockSupport - 当前线程t0已经被唤醒....
10:43:07.722 [t0] INFO com.yg.edu.lock.Juc01_Thread_LockSupport - 准备park住当前线程:t0....
10:43:07.722 [t0] INFO com.yg.edu.lock.Juc01_Thread_LockSupport - 当前线程t0已经被唤醒....
10:43:07.722 [t0] INFO com.yg.edu.lock.Juc01_Thread_LockSupport - 准备park住当前线程:t0....
10:43:07.722 [t0] INFO com.yg.edu.lock.Juc01_Thread_LockSupport - 当前线程t0已经被唤醒....
10:43:07.722 [t0] INFO com.yg.edu.lock.Juc01_Thread_LockSupport - 准备park住当前线程:t0....
10:43:07.722 [t0] INFO com.yg.edu.lock.Juc01_Thread_LockSupport - 当前线程t0已经被唤醒....
.........
.........
.........

后面无限循环,park()执行了之后,没有效果,线程不会阻塞,所以一直重复执行两个log语句。

参照AQS里ReentraintLock源码,

public class TestReentrantLock {
    /**
     * 可重入锁,怎么实现类似于synchronized的功能
     */
    public static ReentrantLock lock = new ReentrantLock(true);

    static boolean flag = false;

    public static void main(String[] args) {
        List<Thread> list = new ArrayList<>();
        for (int i=0;i<10;i++){
            Thread t = new Thread(()->{
                lock.lock();
                System.out.println(Thread.currentThread().getName()+"get lock");
                    //拿到中断信号
                    while (!flag){
                        if(flag){
                            break;
                        }
                    }
                lock.unlock();
            },"t-"+i);
            list.add(t);
            t.start();
        }

        try {
            Thread.sleep(2000);
            list.get(3).interrupt();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }






    }

}

输出:
t-1get lock

两秒后t-3,发出中断信号,解除阻塞

下一轮循环,继续去抢锁,因为获取锁的t-1一直没有释放,故仍旧抢不到,t-3需要继续park()住,也就是阻塞在parkAndCheckInterrupt()里。

按照之前的例子,直接t-3.interrupt(),下次park()是阻塞不住的,重点在于唤醒了之后需要清除中断状态,即Thread.interrupted(),然后才能确保下次park()时能阻塞住。

改造开始的例子,验证

    public static void main(String[] args) {

        Thread t0 = new Thread(new Runnable() {
            @Override
            public void run() {
                Thread current = Thread.currentThread();
                log.info("{},开始执行!", current.getName());
                for (; ; ) {//spin 自旋
                    log.info("准备park住当前线程:{}....", current.getName());
                    LockSupport.park();
                    if (Thread.currentThread().isInterrupted()) {
                        Thread.interrupted();
                        System.out.println("清除中断标记后,线程可以继续park住,如果中断标记为true,即使调用park(),线程也不会阻塞");
                    }
                    log.info("当前线程{}已经被唤醒....", current.getName());
                }
            }
        }, "t0");

        t0.start();

        try {
            Thread.sleep(2000);
            log.info("准备唤醒{}线程!", t0.getName());
            LockSupport.unpark(t0);
            Thread.sleep(10000);
            t0.interrupt();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
输出:

10:58:37.008 [t0] INFO com.yg.edu.lock.Juc01_Thread_LockSupport - t0,开始执行!
10:58:37.011 [t0] INFO com.yg.edu.lock.Juc01_Thread_LockSupport - 准备park住当前线程:t0....
10:58:39.020 [main] INFO com.yg.edu.lock.Juc01_Thread_LockSupport - 准备唤醒t0线程!
10:58:39.020 [t0] INFO com.yg.edu.lock.Juc01_Thread_LockSupport - 当前线程t0已经被唤醒....
10:58:39.020 [t0] INFO com.yg.edu.lock.Juc01_Thread_LockSupport - 准备park住当前线程:t0....
清除中断标记后,线程可以继续park住,如果中断标记为true,即使调用park(),线程也不会阻塞
10:58:49.036 [t0] INFO com.yg.edu.lock.Juc01_Thread_LockSupport - 当前线程t0已经被唤醒....
10:58:49.036 [t0] INFO com.yg.edu.lock.Juc01_Thread_LockSupport - 准备park住当前线程:t0....


评论