线程中断和等待唤醒机制

线程中断和等待唤醒机制

开心 252 2021-12-19

1. 什么是中断

首先,一个线程不应该由其他线程来强制中断或停止,而是应该由线程自己自行停止,所以 Thread.stop、Thread.suspend、Thread.resume 都已经被废弃了
其次在 java 中没有办法立即停止一条线程,然而停止线程却显得尤为重要,如取消一个耗时操作,因此 java 提供了一种用于停止线程的机制-中断
中断只是一种协作机制,java 没有给中断增加任何语法,中断的过程完全需要自己实现
若要中断一个线程,需要手动调用该线程的 interrupt 方法,该方法也仅仅是将线程对象的中断标识设成 true,接着需要写代码不断地检测当前线程的标识位,如果为 true,表示别的线程要求这条线程中断,此时究竟该做什么,需要你自己写代码实现
每个线程对象中都有一个标识,用于表示线程是否被中断,该标识位为 true 表示中断,为 false 表示未中断,通过调用线程对象的 interrupt 方法将该线程的标识位设为 true,可以在别的线程中调用,也可以在自己的线程中调用

2. 如何停止、中断一个运行中的线程

2.1 使用 volatile

public class Test {

    private static volatile boolean isStop = false;

    public static void main(String[] args) {
        new Thread(() -> {
            while (true) {
                if (isStop) {
                    System.out.println("isStop = true,程序结束。");
                    break;
                }
                System.out.println("hello isStop");
            }
        }, "t1").start();

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

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

2.2 使用 atomic

public class Test {

    private static AtomicBoolean atomicBoolean = new AtomicBoolean(false);

    public static void main(String[] args) {
        new Thread(() -> {
            while (true) {
                if (atomicBoolean.get()) {
                    System.out.println("atomicBoolean = true,程序结束");
                    break;
                }
                System.out.println("hello atomic");
            }
        }, "t1").start();

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

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

2.3 使用 interrupt

public class Test {

    public static void main(String[] args) {
        Thread t1 = new Thread(() -> {
            while (true) {
                if (Thread.currentThread().isInterrupted()) {
                    System.out.println("isInterrupted = true,程序结束");
                    break;
                }
                System.out.println("hello interrupt");
            }
        }, "t1");
        t1.start();

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

        new Thread(() -> {
            // 修改 t1 线程的中断标志位为 true
            t1.interrupt();
        }, "t2").start();
    }
}

3. 中断方法介绍及说明

方法描述
void interrupt()设置线程的中断标志为 true,不会停止线程
static boolean interrupted()判断线程是否被中断,并清除当前中断状态,这个方法做了两件事,1返回当前线程的中断状态,2将当前线程的中断状态设置为 false
boolean isInterrupted()通过检查中断标志位判断当前线程是否被中断,它与 interrupted 都是调用了 native boolean isInterrupted(boolean ClearInterrupted),只不过它传入了 false,interrupted 传入了 true

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

  • 如果线程处于正常活动状态,那么会将该线程的中断标志设置为 true,仅此而已,被设置中断标志的线程将继续正常运行不受影响,所以 interrupt 并不能真正的中断线程,需要被调用的线程自己进行配合才行
  • 如果线程处于被阻塞状态(例如处于 sleep、wait、join 等状态) ,在别的线程中调用当前线程对象的 interrpt 方法,那么线程将立即退出被阻塞状态,并抛出一个 InterruptedException 异常
public class Test {

    public static void main(String[] args) {
        Thread t1 = new Thread(() -> {
            while (true) {
                if (Thread.currentThread().isInterrupted()) {
                    System.out.println("isInterrupted = true,程序结束");
                    break;
                }
                try {
                    Thread.sleep(500);
                } catch (InterruptedException e) {
                    // 抛出异常后,中断标识也被清空置为 false,会导致死循环无法停下,所以需要再次调用 interrupt() 将中断标志设置为 true
                    // Thread.currentThread().interrupt();
                    e.printStackTrace();
                }
                System.out.println("hello Interrupt");
            }
        }, "t1");
        t1.start();

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

        new Thread(() -> {
            // 修改 t1 线程的中断标志位为 true
            t1.interrupt();
        }, "t2").start();
    }
}

4. 等待唤醒方法

4.1 Object 的 wait 和 notify

public class ObjectDemo {

    public static void main(String[] args) {

        Object objectLock = new Object();

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

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

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

wait 和 notify 方法必须要在同步块或者方法里面,且成对出现使用
先 wait 后 notify 才能正常使用

4.2 Condition 的 await 和 signal

public class ConditionDemo {

    public static void main(String[] args) {

        Lock lock = new ReentrantLock();
        Condition condition = lock.newCondition();

        new Thread(() -> {
            lock.lock();
            try {
                System.out.println(Thread.currentThread().getName() + "启动");
                condition.await();
                System.out.println(Thread.currentThread().getName() + "被唤醒");
            } catch (InterruptedException e) {
                e.printStackTrace();
            } finally {
                lock.unlock();
            }
        }, "t1").start();

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

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

Condtion 中的线程等待和唤醒方法之前,需要先获取锁
先 await 后 signal 才能使用

4.3 LockSupport 的 park 和 unpark

LockSupport 是用来创建锁和其他同步类的基本线程阻塞原语
LockSupport 使用了一种名为 Permit(许可)的概念来做到阻塞和唤醒线程的功能,每个线程都有一个 permit,permit 只有两个值 1 和 0,默认 0
可以把许可看成是一种(0,1)信号量(Semaphore),但与 Semaphore 不同的是,许可的累加上限是 1

public class LockSupportDemo {

    public static void main(String[] args) {

        Thread t1 = new Thread(() -> {
            System.out.println(Thread.currentThread().getName() + "启动");
            LockSupport.park();
            System.out.println(Thread.currentThread().getName() + "被唤醒");
        }, "t1");
        t1.start();

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

        LockSupport.unpark(t1);
        System.out.println(Thread.currentThread().getName() + "通知了");
    }
}

park 和 unpark 没有先后顺序,但必须成对出现

4.3.1 阻塞

调用 LockSupport.park() 时

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

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

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

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


# java