第四章 LockSupport与线程中断

4.1 线程中断机制

4.1.1 从阿里蚂蚁金服面试题讲起

java.lang.Thread 下的三个方法:

image-1775957503741

  • 如何中断一个运行中的线程?

  • 如何停止一个运行中的线程?

4.1.2 什么是中断机制

首先,一个线程不应该由其他线程来强制中断或停止,而是应该由线程自己自行停止,自己来决定自己的命运,所以Thread.stopThread.suspendThread.resume 都已经被废弃了

其次,在 Java 中没有办法立即停止一条线程,然而停止线程却显得尤为重要,如取消一个耗时操作。因此,Java 提供了一种用于停止线程的协商机制----中断,也即中断标识协商机制

  • 中断只是一种协作协商机制,Java 没有给中断增加任何语法,中断的过程完全需要程序员自行实现。若要中断一个线程,你需要手动调用该线程 interrupt 方法,该方法也仅仅是将该线程对象的中断标识设置为 true,接着你需要自己写代码不断检测当前线程的标识位,如果为 true,表示别的线程请求这条线程中断,此时究竟应该做什么需要你自己写代码实现

  • 每个线程对象都有一个中断标识位,用于表示线程是否被中断;该标识位为 true 表示中断,为 false 表示未中断;通过调用线程对象的 interrupt 方法将该线程的标识位设置为 true;可以在别的线程中调用,也可以在自己的线程中调用

4.1.3 中断的相关API方法之三大方法说明

public void interrupt()

  • 实例方法 Just to set the interrupt flag

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

public static boolean interrupted()

  • 静态方法 Thread.interrupted()

  • 判断线程是否被中断并清除当前中断状态(做了两件事情)

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

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

    3. 这个方法有点不好理解在于如果连续两次调用此方法,则第二次返回 false,因为连续调用两次的结果可能不一样

public boolean isInterrupted()

  • 实例方法

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

4.1.4 大厂面试题中断机制考点

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

  1. 通过一个 volatile 变量实现

    public class InterruptDemo {
    
        private static volatile boolean isStop = false;
    
        /**
         * 运行结果:
         * Hello from a thread!
         * Hello from a thread!
         * ...
         * Hello from a thread!
         * Thread is stopped.
         */
        public static void main(String[] args) {
            new Thread(() -> {
                while (true) {
                    if (isStop) {
                        System.out.println("Thread is stopped.");
                        break;
                    }
                    System.out.println("Hello from a thread!");
                }
            }).start();
    
            try {
                TimeUnit.SECONDS.sleep(5);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
    
            new Thread(() -> {
                isStop = true;
            }).start();
    
        }
    
    }
    
  2. 通过 AutomicBoolean

    public class InterruptDemo {
    
        private static AtomicBoolean atomicBoolean = new AtomicBoolean(false);
    
        /**
         * 运行结果:
         * Hello from a thread!
         * Hello from a thread!
         * ...
         * Hello from a thread!
         * Thread is stopped.
         */
        public static void main(String[] args) {
            new Thread(() -> {
                while (true) {
                    if (atomicBoolean.get()) {
                        System.out.println("Thread is stopped.");
                        break;
                    }
                    System.out.println("Hello from a thread!");
                }
            }).start();
    
            try {
                TimeUnit.SECONDS.sleep(5);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
    
            new Thread(() -> {
                atomicBoolean.set(true);
            }).start();
    
        }
    
    }
    
  3. 通过 Thread 类自带的中断 API 实例方法实现----在需要中断的线程中不断监听中断状态,一旦发生中断,就执行相应的中断处理业务逻辑 stop 线程

    public class InterruptDemo {
    
        /**
         * 运行结果:
         * Hello from a thread!
         * Hello from a thread!
         * ...
         * Hello from a thread!
         * Thread is stopped.
         */
        public static void main(String[] args) {
            Thread t1 = new Thread(() -> {
                while (true) {
                    if (Thread.currentThread().isInterrupted()) {
                        System.out.println("Thread is stopped.");
                        break;
                    }
                    System.out.println("Hello from a thread!");
                }
            });
            t1.start();
            try {
                TimeUnit.SECONDS.sleep(5);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
    
            new Thread(() -> {
                t1.interrupt();
            }).start();
    //        t1.interrupt();
    
        }
    
    }
    

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

答案是不立刻停止,具体来说,当对一个线程,调用 interrupt 时

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

    public class InterruptDemo2 {
    
        /**
         * 执行 interrupt 方法将 t1 标志位设置为 true 后,t1 没有中断,仍然完成了任务后再结束
         * 在 2000 毫秒后,t1 已经结束称为不活动线程,设置状态为没有任何影响
         * 
         * 运行结果:
         * t1线程默认的中断标识:false
         * ----------:1
         * ----------:2
         * ...
         * ----------:300
         * t1线程调用 interrupt() 后的中断标识02:false
         * t1线程调用 interrupt() 后的中断标识01:false
         * t1线程调用 interrupt() 后的中断标识03:false
         */
        public static void main(String[] args) {
            //实例方法 interrupt() 仅仅是设置线程的中断状态位为true,不会停止线程
            Thread t1 = new Thread(() -> {
                for (int i = 1; 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();//true
            System.out.println("t1线程调用 interrupt() 后的中断标识01:" + t1.isInterrupted());
    
            try {
                TimeUnit.MILLISECONDS.sleep(2000);
            } catch (InterruptedException e){
                e.printStackTrace();
            }
            System.out.println("t1线程调用 interrupt() 后的中断标识03:" + t1.isInterrupted());
    
    
        }
    
    }
    
  • 如果线程处于阻塞状态(例如 sleep,wait,join 状态等),在别的线程中调用当前线程对象的 interrupt 方法,那么线程将立刻退出阻塞状态(interrupt 状态也将被清除),并抛出一个 InterruptedException 异常

    public class InterruptDemo3 {
    
      /**
         * 1. 中断标志位默认为false
         * 2. t2 对 t1 发出中断协商  t1.interrupt();
         * 3. 中断标志位为true: 正常情况 程序停止
         *    中断标志位为true  异常情况, InterruptedException ,将会把中断状态清楚,中断标志位为 false
         * 4. 需要在 catch 块中,再次调用 interrupt() 方法将中断标志位设置为 false;
         * 
         * 运行结果:
         * ----- hello InterruptDemo3
         * ----- hello InterruptDemo3
         * Thread-0中断标志位:true 程序停止
         * java.lang.InterruptedException: sleep interrupted
         * 	at java.lang.Thread.sleep(Native Method)
         * 	at com.my.demo.InterruptDemo3.lambda$main$0(InterruptDemo3.java:23)
         * 	at java.lang.Thread.run(Thread.java:748)
         */
        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(1000);
                    } catch (InterruptedException e) {
                        //为什么要在异常出,再调用一次
                        Thread.currentThread().interrupt();
                        e.printStackTrace();
                    }
    
                    System.out.println("----- hello InterruptDemo3");
                }
            });
            t1.start();
    
            //暂停几秒
            try {
                TimeUnit.SECONDS.sleep(1);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
    
            new Thread(() -> {
                t1.interrupt();
            }, "t2").start();
    
        }
    
    }
    

    对于当前情况的源码分析如下:

    image-1776034884109

    总之,需要记住的是中断只是一种协商机制,修改中断标识位仅此而已,不是立刻 stop 打断

静态方法 Thread.interrupted(),谈谈你的理解?

image-1776035224525

public class InterruptDemo4 {

    /**
     * 测试当前线程是否被中断(检查中断标识),返回一个 boolean 并清除中断状态,
     * 第二次再调用时中断状态已经被清除,将返回一个 false
     * 
     * 运行结果:
     * main	false
     * main	false
     * ----1
     * main	true
     * main	false
     */
    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();
        System.out.println(Thread.currentThread().getName() + "\t" + Thread.interrupted());
        System.out.println(Thread.currentThread().getName() + "\t" + Thread.interrupted());
    }

}

对于静态方法 Thread.interrupted() 和实例方法 isInterrupted()的区别在于:

  • 静态方法interrupted 将会清除中断状态(传入的参数 ClearInterrupted 为 true)

  • 实例方法 isInterrupted 则不会(传入的参数 ClearInterrupted 为 false)

image-1776035919786

4.1.5 总结

  1. public void interrupt()是一个实例方法,它通知目标线程中断,也仅仅是设置目标线程的中断标识位为 true

  2. public boolean isInterrupted()是一个实例方法,它判断当前线程是否被中断(通过检查中断标志位)并获取中断标志

  3. public static boolean interrupted()是一个静态方法,返回当前线程的中断真实状态(boolean 类型),后会将当前线程的中断状态设置为 false,此方法调用之后会清除当前线程的中断标志位(将中断标志置为 false),返回当前值并清零置为 false

4.2 LockSupport 是什么

LockSupport 是用来创建锁和其他同步类的基本线程阻塞原语,其中park()unpack()而作用分别是阻塞线程和解除阻塞线程

4.3 线程等待唤醒机制

4.3.1 三种让线程等待和唤醒的方法

  1. 使用 Object 中的 wait() 方法让线程等待,使用 Object 中的notify()方法唤醒线程

  2. 使用 JUC 包中的 Condition 的await()方法让线程等待,使用signal()方法唤醒线程

  3. LockSupport 类可以阻塞当前线程以及唤醒指定被阻塞的线程

4.3.2 Object 类中的 wait 和 notify 方法实现线程等待和唤醒

  • wait 和 notify 方法必须要在同步代码块或者方法里面,且成对出现使用

  • 先 wait 再 notify 才 ok

public class LockSupportDemo {

    /**
     * 运行结果:
     * t1	 ----------com in
     * t2	 ----------发出通知
     * t1	 ----------被唤醒
     */
    public static void main(String[] args) {
        Object objectLock = new Object();
        new Thread(() -> {
            synchronized (objectLock) {
                System.out.println(Thread.currentThread().getName() + "\t ----------com in");
                try {
                    objectLock.wait();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                System.out.println(Thread.currentThread().getName() + "\t ----------被唤醒");
            }

        }, "t1").start();

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

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

    }

}

4.3.3 Condition 接口中的 await 和 signal 方法实现线程的等待和唤醒

  • Condition 中的线程等待和唤醒方法,需要先获取锁

  • 一定要先 await 后 signal,不要反了

public class LockSupportDemo {

	/**
	 * 运行结果:
     * t1	 ----------com in
     * t2	 ----------发出通知
     * t1	 ----------被唤醒
     */
    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() + "\t ----------com in");
                condition.await();
                System.out.println(Thread.currentThread().getName() + "\t ----------被唤醒");
            } catch (InterruptedException e) {
                e.printStackTrace();
            } finally {
                lock.unlock();
            }
        }, "t1").start();

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

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

    }

}

4.3.4 上述两个对象 Object 和 Condition 使用的限制条件

  • 线程需要先获得并持有锁,必须要在锁块(synchronized 或 lock)中

  • 必须要先等待后唤醒,线程才能被唤醒

4.3.5 LockSupport 类中的 park 等待和 unpark 唤醒

是什么?

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

  • LockSupport 类使用了一种名为 Permit(许可)的概念来做到阻塞和唤醒线程的功能,每个线程都有一个许可(Permit),许可证只能有一个,累加上线是 1

主要方法

  • 阻塞:Permit 许可证默认没有不能放行,所以一开始调用 park() 方法当前线程会阻塞,直到别的线程给当前线程发放 Permit,park 方法才会被唤醒

    park/park(Object blocker) ----- 阻塞当前线程/阻塞传入的具体线程

  • 唤醒:调用 unpack(thread)方法后,就会将 thread 线程的许可证 Permit 发放,会自动唤醒 park 线程,即之前阻塞中的 LockSupport.park() 方法会立刻返回

    unpark(Thread thread) ----- 唤醒处于阻塞状态的指定线程

public class LockSupportDemo {

    /**
     * 运行结果:
     * t1	 ----------com in
     * t2	 ----------发出通知
     * t1	 ----------被唤醒
     */
    public static void main(String[] args) {
        Thread t1 = new Thread(() -> {
            System.out.println(Thread.currentThread().getName() + "\t ----------com in");
            LockSupport.park();
            System.out.println(Thread.currentThread().getName() + "\t ----------被唤醒");
        }, "t1");
        t1.start();

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

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

    }

}

重点说明(重要)

  • LockSupport 是用来创建锁和其他同步类的基本线程阻塞原语,所有的方法都是静态方法,可以让线程在任意位置阻塞,阻塞后也有对应的唤醒方法。归根结底,LockSupport 是调用 Unsafe 中的 native 代码

  • LockSupport 提供park()unpark()方法实现阻塞线程和解除线程阻塞的过程,LockSupport 和每个使用它的线程都有一个许可(Permit)关联,每一个线程都有一个相关的 Permit,Permit 最多只有一个,重复调用 unpark 也不会积累凭证

  • 形象理解:线程阻塞需要消耗凭证(Permit),这个凭证最多只有一个

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

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

面试题

为什么 LockSupport 可以突破 wait/notify 的原有调用顺序?

  • 因为 unpark 获得了一个凭证,之后再调用 park 方法,就可以名正言顺的凭证消费,故不会阻塞,先发放了凭证后续可以通畅无阻

为什么唤醒两次后阻塞两次,但最终结果还会阻塞线程?

  • 因为凭证的数量最多为 1,连续调用两次 unpark 和调用 unpark 效果一样,只会增加一个凭证,而调用两次 park 却需要消费两个凭证,证不够,不能放行
posted @ 2026-04-13 21:33  清风含薰  阅读(3)  评论(0)    收藏  举报