搞懂多线程(五)之线程中断、等待、唤醒

一、线程中断

方式一:volatile关键字

  • 通过volatile关键字的可见性修饰一个标识,要停止的线程监听该标识

方式二:AtomicBoolean原子类

  • 通过AtomicBoolean原子布尔类,修饰一个标识,要停止的线程监听该标识

方式三:interrupt()

  • 通过Thread中的interrupt()实现

    • 1、通过Thread.currentThread().isInterrupted()判断线程中断状态

    • 2、调用要中断线程的interrupt()来设置中断状态(要中断的线程不一定立马中断)

Thread中中断方法的特殊说明

  • public void interrupt():中断此线程

    • 仅仅是设置线程的中断状态为true,发起一个协商而不会立刻停止线程

    • 如果该线程处于阻塞状态(调用wait() join() sleep() 引起的)那么它的中断状态将被清除,并且将收到InterruptedException ,如有必要需在wait() join() sleep() 的finally中手动中断状态

    • 中断不活动的线程不会产生任何影响,线程如果已经结束,则返回false

  • public boolean isInterrupted():判断当前线程是否被中断(通过检查中断标志位)

  • public static boolean interrupted():判断线程是否被中断并清除当前中断状态。

    • 1、返回当前线程的中断状态,测试当前线程是否已被中断

    • 2、将当前线程的中断状态清零并重新设为false,清除线程的中断状态

    • 如果连续两次调用此方法,则第二次调用将返回false,因为连续调用两次的结果可能不一样

二、线程等待和唤醒

方式一:使用Object中的wait()和notify()

synchronized配合使用(需要先持有锁)wait()notify()实现等待和唤醒,wait()应在notify()方法调用之前调用

public static void main(String[] args) {

    Object o = new Object();

    new Thread(() -> {
        synchronized (o) {
            System.out.println(Thread.currentThread().getName() + " AA进入");
            try {
                o.wait();
                System.out.println(Thread.currentThread().getName() + " AA结束");
            } catch (InterruptedException e) {
                throw new RuntimeException(e);
            }
        }
    }, "AA").start();

    new Thread(() -> {
        synchronized (o) {
            System.out.println(Thread.currentThread().getName() + " BB进入");
            o.notify();
        }
    }, "BB").start();

}

输出结果

AA AA进入
BB BB进入
AA AA结束

方式二:使用ReentrantLock.Condition中的await()和signal()

ReentrantLock配合使用,(需要先持有锁)调用newCondition()中的await()signal()实现等待和唤醒,await()应在signal()方法调用之前调用

public static void main(String[] args) {

    ReentrantLock reentrantLock = new ReentrantLock();

    Condition condition = reentrantLock.newCondition();

    new Thread(() -> {
        try {
            reentrantLock.lock();
            System.out.println(Thread.currentThread().getName() + " AA进入");
            condition.await();
            System.out.println(Thread.currentThread().getName() + " AA结束");
        } catch (InterruptedException e) {
            throw new RuntimeException(e);
        } finally {
            reentrantLock.unlock();
        }

    }, "AA").start();

    try {
        TimeUnit.SECONDS.sleep(1);
    } catch (InterruptedException e) {
        throw new RuntimeException(e);
    }

    new Thread(() -> {
        try {
            reentrantLock.lock();
            System.out.println(Thread.currentThread().getName() + " BB进入");
            condition.signal();
        } finally {
            reentrantLock.unlock();
        }

    }, "BB").start();

}

输出结果

AA AA进入
BB BB进入
AA AA结束

方式三:使用LockSupport中的park()和unpark​()

LockSupport:用于创建锁和其他同步类的基本线程阻塞原语。形象的理解:线程阻塞需要消耗凭证(permit),这个凭证最多只有1个。

  • 调用park()方法

    • 如果有凭证,则会直接消耗掉这个凭证然后正常退出

    • 如果无凭证,就必须阻塞等待凭证可用

  • 调用unpark()方法

    • 它会增加一个凭证,但凭证最多只能有1个,累加无效

park()unpark()使用时无需持有锁,顺序也可调换,但是就算unpark()多次调用也是只能只有一个许可证只会触发一次park()效果

public static void main(String[] args) {

    Thread t1 = new Thread(() -> {
        System.out.println(Thread.currentThread().getName() + " AA进入");
        LockSupport.park();
        System.out.println(Thread.currentThread().getName() + " AA结束");
    }, "AA");
    t1.start();

    new Thread(() -> {
        System.out.println(Thread.currentThread().getName() + " BB进入");
        LockSupport.unpark(t1);
    }, "BB").start();
}