文章目录:

1.什么是中断机制?

2.如何停止中断运行中的线程?

2.1 通过一个volatile变量实现

2.2 通过AtomicBoolean原子布尔类

2.3 通过Thread类自带的中断API方法实现

3.Thread类的三大API说明

3.1 实例方法interrupt(),没有返回值

3.2 实例方法isInterrupted(),返回布尔值

3.3 当前线程的中断标识为true,是不是线程就立刻停止?

3.4 在3.3中断程序的基础上,添加sleep睡眠

3.5 静态方法public static boolean interrupted()

4.LockSupport

4.1 线程的等待唤醒机制

4.2 wait、notify

4.3 await、signal

4.4 park、unpark


1.什么是中断机制?

  • 首先,一个线程不应该由其他线程来强制中断或停止,而是应该由线程自己自行停止。所以,Thread.stop, Thread.suspend, Thread.resume 都已经被废弃了。
  • 其次,在Java中没有办法立即停止一条线程,然而停止线程却显得尤为重要,如取消一个耗时操作。

                  因此,Java提供了一种用于停止线程的协商机制——中断。

                  中断只是一种协作协商机制,Java没有给中断增加任何语法,中断的过程完全需要程序员自己实现。

                  若要中断一个线程,你需要手动调用该线程的interrupt方法,该方法也仅仅是将线程对象的中断标识设成true;

                  接着你需要自己写代码不断地检测当前线程的标识位,如果为true,表示别的线程要求这条线程中断,此时究竟该做什么需要你自己写代码实现。

                  每个线程对象中都有一个标识,用于表示线程是否被中断;该标识位为true表示中断,为false表示未中断;

                  通过调用线程对象的interrupt方法将该线程的标识位设为true;可以在别的线程中调用,也可以在自己的线程中调用。

尚硅谷周阳老师的例子:顾客在无烟餐厅中吸烟,服务员希望他别吸烟了,不是强行停止他吸烟,而是给他的标志位打为true,具体的停止吸烟还是要顾客自己停止。(体现了协商机制)

  • 中断相关的三大API方法如下图:↓↓↓

Java——聊聊JUC中的线程中断机制 & LockSupport_java

Java——聊聊JUC中的线程中断机制 & LockSupport_java_02


2.如何停止中断运行中的线程?

2.1 通过一个volatile变量实现

package com.szh.demo.interrupt;

import java.util.concurrent.TimeUnit;

public class InterruptDemo1 {
    static volatile boolean isStop = false;

    public static void main(String[] args) {
        new Thread(() -> {
            while (true) {
                if (isStop) {
                    System.out.println(Thread.currentThread().getName() + " isStop被修改为true,线程停止");
                    break;
                }
                System.out.println(Thread.currentThread().getName() + " hello volatile....");
            }
        }, "t1").start();

        try {
            TimeUnit.MILLISECONDS.sleep(20);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }

        new Thread(() -> {
            isStop = true;
        }, "t2").start();
    }
}

Java——聊聊JUC中的线程中断机制 & LockSupport_java_03

2.2 通过AtomicBoolean原子布尔类

package com.szh.demo.interrupt;

import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicBoolean;

public class InterruptDemo2 {
    static AtomicBoolean atomicBoolean = new AtomicBoolean(false);

    public static void main(String[] args) {
        new Thread(() -> {
            while (true) {
                if (atomicBoolean.get()) {
                    System.out.println(Thread.currentThread().getName() + " isStop被修改为true,线程停止");
                    break;
                }
                System.out.println(Thread.currentThread().getName() + " hello AtomicBoolean....");
            }
        }, "t1").start();

        try {
            TimeUnit.MILLISECONDS.sleep(20);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }

        new Thread(() -> {
            atomicBoolean.set(true);
        }, "t2").start();
    }
}

Java——聊聊JUC中的线程中断机制 & LockSupport_System_04

2.3 通过Thread类自带的中断API方法实现

package com.szh.demo.interrupt;

import java.util.concurrent.TimeUnit;

public class InterruptDemo3 {

    public static void main(String[] args) {
        Thread t1 = new Thread(() -> {
            while (true) {
                if (Thread.currentThread().isInterrupted()) {
                    System.out.println(Thread.currentThread().getName() + " isStop被修改为true,线程停止");
                    break;
                }
                System.out.println(Thread.currentThread().getName() + " hello isInterrupted....");
            }
        }, "t1");
        t1.start();

        try {
            TimeUnit.MILLISECONDS.sleep(20);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }

        new Thread(() -> {
            t1.interrupt();
        }, "t2").start();
    }
}

Java——聊聊JUC中的线程中断机制 & LockSupport_java_05


3.Thread类的三大API说明

3.1 实例方法interrupt(),没有返回值

这个interrupt()实例方法,底层实际上调用了interrupt0()这个方法,根据后面的注释可以看到,仅仅是设置中断标识位,而interrupt0这个方法是一个native方法,底层又调用了C。

Java——聊聊JUC中的线程中断机制 & LockSupport_java_06

Java——聊聊JUC中的线程中断机制 & LockSupport_LockSupport_07

而在jdk官方文档中可以看到有关这个方法的叙述。

Java——聊聊JUC中的线程中断机制 & LockSupport_System_08

3.2 实例方法isInterrupted(),返回布尔值

这个实例方法的底层调用了一个native方法,传入了一个布尔值,而这个值就是 是否清除中断标识位,false表示不清除,true表示清除(即将线程的中断标识位清除重新设置为false)。

Java——聊聊JUC中的线程中断机制 & LockSupport_java_09

Java——聊聊JUC中的线程中断机制 & LockSupport_System_10

具体来说,当对一个线程,调用 interrupt() 时:


① 如果线程处于正常活动状态,那么会将该线程的中断标志设置为 true,仅此而已。被设置中断标志的线程将继续正常运行,不受影响。所以, interrupt() 并不能真正的中断线程,需要被调用的线程自己进行配合才行。

② 如果线程处于被阻塞状态(例如处于sleep, wait, join 等状态),在别的线程中调用当前线程对象的interrupt方法,那么线程将立即退出被阻塞状态(中断状态将被清除),并抛出一个InterruptedException异常。

(中断不活动的线程不会产生任何影响,看下面案例)

3.3 当前线程的中断标识为true,是不是线程就立刻停止?

  • 否,仅仅设置了一个中断状态
  • 看看中断是否会立即停止这个300的线程。否,虽然中断标志位变了。但是i一直输完300次,才最终停止。
package com.szh.demo.interrupt;

import java.util.concurrent.TimeUnit;

public class InterruptDemo4 {

    public static void main(String[] args) {
        Thread t1 = new Thread(() -> {
            for (int i = 0; i < 300; i++) {
                System.out.println("--------- " + i);
            }
            System.out.println("t1调用interrupt()之后的中断标识02---- " + Thread.currentThread().isInterrupted());
        }, "t1");
        t1.start();
        System.out.println("t1线程默认的中断标识---- " + t1.isInterrupted());

        try {
            TimeUnit.MILLISECONDS.sleep(2);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        t1.interrupt();
        System.out.println("t1调用interrupt()之后的中断标识01----  " + t1.isInterrupted());
    }
}

Java——聊聊JUC中的线程中断机制 & LockSupport_interrupt_11

Java——聊聊JUC中的线程中断机制 & LockSupport_LockSupport_12

Java——聊聊JUC中的线程中断机制 & LockSupport_interrupt_13

对上面的代码稍作改变,如下:↓↓↓

package com.szh.demo.interrupt;

import java.util.concurrent.TimeUnit;

public class InterruptDemo5 {

    public static void main(String[] args) {
        Thread t1 = new Thread(() -> {
            for (int i = 0; i < 300; i++) {
                System.out.println("--------- " + i);
            }
            System.out.println("after t1.interrupt()---第2次---- " + Thread.currentThread().isInterrupted());
        }, "t1");
        t1.start();
        System.out.println("before t1.interrupt()---- " + t1.isInterrupted());

        try {
            TimeUnit.MILLISECONDS.sleep(2);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        t1.interrupt();
        System.out.println("after t1.interrupt()---第1次--- " + t1.isInterrupted());
        try {
            TimeUnit.MILLISECONDS.sleep(2000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("after t1.interrupt()---第3次--- " + t1.isInterrupted());
    }
}

Java——聊聊JUC中的线程中断机制 & LockSupport_java_14

Java——聊聊JUC中的线程中断机制 & LockSupport_java_15

Java——聊聊JUC中的线程中断机制 & LockSupport_java_16

在输出结果中,我们可以看到和我们预想的都一样,只有最后一行输出,t1线程它自己不是已经打断了吗?那中断标识就应该是 true 啊?为什么变成了false???

原因是上面的代码中,t1线程打印300次i,而最后一行输出代码是在2000ms之后的,t1线程是完全可以在这个时间内完成300次i的打印工作,所以程序运行到最后一行输出,t1线程已经结束死亡了,再根据 interrupt 方法api中的这句话:

  • 中断不存在的线程不需要任何效果。

我们就懂了,中断不存在的线程没什么意义的,所以这里的中断标识自然就恢复成了默认值 false。

3.4 在3.3中断程序的基础上,添加sleep睡眠

package com.szh.demo.interrupt;

import java.util.concurrent.TimeUnit;

public class InterruptDemo6 {

    public static void main(String[] args) {
        Thread t1 = new Thread(() -> {
            while (true) {
                if (Thread.currentThread().isInterrupted()) {
                    System.out.println(Thread.currentThread().getName() + " 中断标识位:" +
                            Thread.currentThread().isInterrupted() + " 线程终止....");
                    break;
                }
                try {
                    Thread.sleep(200);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                System.out.println("---- hello InterruptDemo6");
            }
        }, "t1");
        t1.start();

        try {
            TimeUnit.SECONDS.sleep(1);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }

        new Thread(() -> {
            t1.interrupt();
        }, "t2").start();
    }
}

这个程序是停不下来的,我是不想耗费太多CPU资源,手动停止了。 

原因就是:

  • 如果该线程阻塞的调用wait() , wait(long) ,或wait(long, int)的方法Object类,或者在join() , join(long) , join(long, int) , sleep(long) ,或sleep(long, int) ,这个类的方法,那么它的中断状态将被清除,并且将收到一个InterruptedException 。
  • 所以这个时候线程t1的中断标识位被清除,恢复成了false,那么久永远也停不下来了。

Java——聊聊JUC中的线程中断机制 & LockSupport_System_17

如何修改上面的代码,使得程序正常运行停止呢?   → 

  1. 中断标志位 默认是false。
  2. t2 ----->t1发出了中断协商,t2调用t1.interrupt(),中断标志位true。
  3. 中断标志位true,正常情况下,程序停止。
  4. 中断标志位true,异常情况下,InterruptedException,将会把中断状态清除,并且将收到InterruptedException。中断标志位false导致无限循环。
  5. 在catch块中,需要再次给中断标志位设置为true,2次调用停止。
package com.szh.demo.interrupt;

import java.util.concurrent.TimeUnit;

public class InterruptDemo6 {

    public static void main(String[] args) {
        Thread t1 = new Thread(() -> {
            while (true) {
                if (Thread.currentThread().isInterrupted()) {
                    System.out.println(Thread.currentThread().getName() + " 中断标识位:" +
                            Thread.currentThread().isInterrupted() + " 线程终止....");
                    break;
                }
                try {
                    Thread.sleep(200);
                } catch (InterruptedException e) {
                    Thread.currentThread().interrupt(); //关键代码
                    e.printStackTrace();
                }
                System.out.println("---- hello InterruptDemo6");
            }
        }, "t1");
        t1.start();

        try {
            TimeUnit.SECONDS.sleep(1);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }

        new Thread(() -> {
            t1.interrupt();
        }, "t2").start();
    }
}

Java——聊聊JUC中的线程中断机制 & LockSupport_java_18

3.5 静态方法public static boolean interrupted()

静态方法,Thread.interrupted();判断线程是否被中断,并清除当前中断状态这个方法做了两件事:1 返回当前线程的中断状态    2 将当前线程的中断状态设为false(这个方法有点不好理解,因为连续调用两次的结果可能不一样。)

package com.szh.demo.interrupt;

public class InterruptDemo7 {

    public static void main(String[] args) {
        System.out.println(Thread.currentThread().getName() + "\t" + Thread.interrupted());
        System.out.println(Thread.currentThread().getName() + "\t" + Thread.interrupted());
        System.out.println("-----1");
        Thread.currentThread().interrupt();//中断标志位设置为true
        System.out.println("-----2");
        System.out.println(Thread.currentThread().getName() + "\t" + Thread.interrupted());
        System.out.println(Thread.currentThread().getName() + "\t" + Thread.interrupted());
    }
}

前两次调用没啥说的,因为main主线程并没有中断,第三次调用的时候,因为上面已经 interrupt 了,所以被中断了,这里中断标识位肯定就是 true。此时这个静态方法在中断之后第一次调用(返回当前线程的中断状态,被中断了就是true;第二件事,将当前线程的中断标识重置为false)。所以当最后一行再次调用它的时候,就是false了。 

Java——聊聊JUC中的线程中断机制 & LockSupport_interrupt_19

看一下这个静态方法的源码:↓↓↓    在那个isInterrupt实例方法中传入的 一个布尔值,而这个值就是 是否清除中断标识位,false表示不清除,true表示清除(即将线程的中断标识位清除重新设置为false)。


这两个方法在底层都调用了native方法isInterrupted。  只不过传入参数ClearInterrupted一个传参传了true,一个传了false。


静态方法interrupted() 中true表示清空当前中断状态。  实例方法isInterrupted 则不会。

Java——聊聊JUC中的线程中断机制 & LockSupport_LockSupport_20


4.LockSupport

用于创建锁和其他同步类的基本线程阻塞原语。

这个类与每个使用它的线程相关联,一个许可证(在Semaphore类的意义上)。 如果许可证可用,则呼叫parkpark返回,在此过程中消耗它; 否则可能会阻止。 致电unpark使许可证可用,如果尚不可用。 (与信号量不同,许可证不能累积,最多只有一个。)

Java——聊聊JUC中的线程中断机制 & LockSupport_interrupt_21

核心就是park()unpark()方法

  • park()方法是阻塞线程
  • unpark()方法是解除阻塞线程

4.1 线程的等待唤醒机制

  1. 使用Object中的wait()方法让线程等待,使用Object中的notify()方法唤醒线程。(有局限性)
  2. 使用JUC包中Conditionawait()方法让线程等待,使用signal()方法唤醒线程。(有局限性)
  3. LockSupport类可以阻塞当前线程以及唤醒指定被阻塞的线程。

4.2 wait、notify

package com.szh.demo.locksupport;

import java.util.concurrent.TimeUnit;

public class LockSupportDemo1 {
    public static void main(String[] args) {
        final Object obj = new Object();

        new Thread(() -> {
            synchronized (obj) {
                System.out.println(Thread.currentThread().getName() + " --- come in");
                try {
                    obj.wait();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
            System.out.println(Thread.currentThread().getName() + " --- 被唤醒了");
        }, "t1").start();

        try {
            TimeUnit.SECONDS.sleep(3L);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }

        new Thread(() -> {
            synchronized (obj) {
                obj.notify();
                System.out.println(Thread.currentThread().getName() + " --- 发出通知");
            }
        }, "t2").start();
    }
}

Java——聊聊JUC中的线程中断机制 & LockSupport_juc_22

异常情况1:将 synchronized 同步代码块对应的代码注释掉。 

package com.szh.demo.locksupport;

import java.util.concurrent.TimeUnit;

public class LockSupportDemo1 {
    public static void main(String[] args) {
        final Object obj = new Object();

        new Thread(() -> {
            //synchronized (obj) {
                System.out.println(Thread.currentThread().getName() + " --- come in");
                try {
                    obj.wait();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            //}
            System.out.println(Thread.currentThread().getName() + " --- 被唤醒了");
        }, "t1").start();

        try {
            TimeUnit.SECONDS.sleep(3L);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }

        new Thread(() -> {
            //synchronized (obj) {
                obj.notify();
                System.out.println(Thread.currentThread().getName() + " --- 发出通知");
            //}
        }, "t2").start();
    }
}

Java——聊聊JUC中的线程中断机制 & LockSupport_java_23

异常情况2:将wait和notify顺序调换。 

package com.szh.demo.locksupport;

import java.util.concurrent.TimeUnit;

public class LockSupportDemo1 {
    public static void main(String[] args) {
        final Object obj = new Object();

        new Thread(() -> {
            try {
                TimeUnit.SECONDS.sleep(3L);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            synchronized (obj) {
                System.out.println(Thread.currentThread().getName() + " --- come in");
                try {
                    obj.wait();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
            System.out.println(Thread.currentThread().getName() + " --- 被唤醒了");
        }, "t1").start();

//        try {
//            TimeUnit.SECONDS.sleep(3L);
//        } catch (InterruptedException e) {
//            e.printStackTrace();
//        }

        new Thread(() -> {
            synchronized (obj) {
                obj.notify();
                System.out.println(Thread.currentThread().getName() + " --- 发出通知");
            }
        }, "t2").start();
    }
}

Java——聊聊JUC中的线程中断机制 & LockSupport_java_24

小总结

  • 线程先要获得并持有锁,必须在锁块(synchronized或lock)中
  • 必须要先等待后唤醒,线程才能够被唤醒。要保证先wait,后notify才OK。
  • wait和notify方法必须要在同步块或者方法里面,且成对出现使用。

4.3 await、signal

package com.szh.demo.locksupport;

import javax.swing.*;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;

public class LockSupportDemo2 {
    private static Lock lock = new ReentrantLock();
    private static Condition condition = lock.newCondition();

    public static void main(String[] args) {
        new Thread(() -> {
            lock.lock();
            try {
                System.out.println(Thread.currentThread().getName() + " --- come in");
                condition.await();
                System.out.println(Thread.currentThread().getName() + " --- 被唤醒了");
            } catch (InterruptedException e) {
                e.printStackTrace();
            } finally {
                lock.unlock();
            }
        }, "t1").start();

        try {
            TimeUnit.SECONDS.sleep(1L);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }

        new Thread(() -> {
            lock.lock();
            try {
                condition.signal();
                System.out.println(Thread.currentThread().getName() + " --- 发出通知");
            } finally {
                lock.unlock();
            }
        }, "t2").start();
    }
}

Java——聊聊JUC中的线程中断机制 & LockSupport_java_25

异常情况1:将对应的加锁解锁的代码注释掉,报错信息和第一个案例是一样的。 

异常情况2:先进行 signal,再进行 await,报错信息和第一个案例是一样的。 

小总结

  • 线程先要获得并持有锁,必须在锁块(synchronized或lock)中
  • 必须要先等待后唤醒,线程才能够被唤醒。一定要先await后signal,不能反了
  • Condition中的线程等待和唤醒方法,需要先获取锁

4.4 park、unpark

调用LockSupport.park()时,发现它调用了unsafe类,并且默认传了一个0。


permit默认是零,所以一开始调用park()方法,当前线程就会阻塞,直到别的线程将当前线程的permit设置为1时,park方法会被唤醒,然后会将permit再次设置为零并返回。

调用LockSupport.unpark();时,也调用了unsafe类。


调用unpark(thread)方法后,就会将thread线程的许可permit设置成1(注意多次调用unpark方法,不会累加,permit值还是1)会自动唤醒thread线程,即之前阻塞中的LockSupport.park()方法会立即返回。

解决上面两个案例的第一个问题:必须放在锁块中,LockSupport不需要这样做。 

package com.szh.demo.locksupport;

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

public class LockSupportDemo3 {

    public static void main(String[] args) {
        Thread t1 = new Thread(() -> {
            System.out.println(Thread.currentThread().getName() + " --- come in");
            LockSupport.park();
            System.out.println(Thread.currentThread().getName() + " --- 被唤醒了");
        }, "t1");
        t1.start();

        try {
            TimeUnit.SECONDS.sleep(2L);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }

        new Thread(() -> {
            LockSupport.unpark(t1);
            System.out.println(Thread.currentThread().getName() + " --- 发出通知");
        }, "t2").start();
    }
}

Java——聊聊JUC中的线程中断机制 & LockSupport_LockSupport_26

解决上面两个案例的第一个问题:必须先等待,后唤醒,LockSupport不需要这样做,先唤醒后等待照样OK。 

package com.szh.demo.locksupport;

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

public class LockSupportDemo3 {

    public static void main(String[] args) {
        Thread t1 = new Thread(() -> {
            try {
                TimeUnit.SECONDS.sleep(3L);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println(Thread.currentThread().getName() + " --- come in " + System.currentTimeMillis());
            LockSupport.park();
            System.out.println(Thread.currentThread().getName() + " --- 被唤醒了 " + System.currentTimeMillis());
        }, "t1");
        t1.start();

        new Thread(() -> {
            LockSupport.unpark(t1);
            System.out.println(Thread.currentThread().getName() + " --- 发出通知");
        }, "t2").start();
    }
}

这里会先执行t2线程的unpark方法,此时t1线程手中就有了一张许可证,当t1线程睡眠3秒之后,执行代码,走到park方法不会再阻塞,直接拿出许可证,继续向下执行,所以看代码的花费时间就知道,这里的park是无效没有阻塞的。 

Java——聊聊JUC中的线程中断机制 & LockSupport_juc_27

jdk官方文档中说了,与信号量不同,许可证不能累积,最多只有一个。

老子就不信这个邪,我非得给你来两个许可证,看看下面的代码。

package com.szh.demo.locksupport;

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

public class LockSupportDemo3 {

    public static void main(String[] args) {
        Thread t1 = new Thread(() -> {
            try {
                TimeUnit.SECONDS.sleep(3L);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println(Thread.currentThread().getName() + " --- come in " + System.currentTimeMillis());
            LockSupport.park();
            LockSupport.park();
            System.out.println(Thread.currentThread().getName() + " --- 被唤醒了 " + System.currentTimeMillis());
        }, "t1");
        t1.start();

        new Thread(() -> {
            LockSupport.unpark(t1);
            LockSupport.unpark(t1);
            System.out.println(Thread.currentThread().getName() + " --- 发出通知");
        }, "t2").start();
    }
}

Java——聊聊JUC中的线程中断机制 & LockSupport_interrupt_28

可以看到,代码卡在这里了,这是因为你虽然发了两个许可证,但是最多只能持有一个,那么当第二次park尝试再去获取许可证时,已经不可能了,因为t1线程手中的那个许可证已经被第一次park的时候消费掉了。


当调用park方法时如果有凭证,则会直接消耗掉这个凭证然后正常退出;如果无凭证,就必须阻塞等待凭证可用。

而unpark则相反, 它会增加一个凭证, 但凭证最多只能有1个, 累加无效。 

针对park和unpark方法的代码实测结论:

  1. park:unpark = 1:1,代码正常执行无误。
  2. park:unpark = 1:n,代码正常执行无误。(尽管unpark了多次,但是当前线程最多只能持有1个许可证,之后也只park了一次,消费了一个许可证,所以没问题,但还是不推荐这样写)
  3. park:unpark = n:1,代码卡死无法结束。(当前线程最多只能持有1个许可证,park一次消费一个,park多次直接无证,当前线程无法正常结束)
  4. park:unpark = n:n,代码卡死无法结束。(原因在上面说过了)