更多详情内容请访问:JUC 系列文章导读

1、中断机制

1.1 什么是中断机制

一个线程不应该由其他线程来强制中断或停止,而是应该由线程自己自行停止。因此 Thread.stop()Thread.suspend()Thread.resume() 这些方法都已经被废弃了

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

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

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

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

1.2 中断相关 API 中三大方法

方法 说明
public void interrupt() 实例方法,仅仅是设置线程的中断状态为 true,发起一个协商而不会立刻停止线程
public static boolean interrupted() 静态方法,判断线程是否被中断,并清除当前中断状态
这个方法做了两件事:①返回当前线程的中断状态;②将当前线程的中断状态设为 false
public boolean isInterrupted() 实例方法,判断当前线程是否被中断(通过检查中断标志位)

1.2.1 interrupt()

image-20220811164803101

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

  1. 如果线程处于正常活动状态,那么会将该线程的中断标志设置为 true,仅此而已。被设置中断标志的线程将继续正常运行,不受影响。所以, interrupt() 并不能真正的中断线程,需要被调用的线程自己进行配合才行。
  2. 如果线程处于被阻塞状态(例如处于 sleepwaitjoin等状态),在别的线程中调用当前线程对象的interrupt() 方法,那么线程将立即退出被阻塞状态(中断状态将被清除),并抛出一个InterruptedException 异常
  3. 中断不活动的线程不会产生任何影响
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()+"\t " + "中断标志位:"+Thread.currentThread().isInterrupted()+" 程序停止");
                    break;
                }

                try {
                    Thread.sleep(200);
                } catch (InterruptedException e) {
                    /**
                     * 1、中断标志位,默认false
                     * 2、t2 ----> t1发出了中断协商,t2调用t1.interrupt(),中断标志位true
                     * 3、中断标志位true,正常情况,程序停止,^_^
                     * 4、中断标志位true,异常情况,InterruptedException,将会把中断状态将被清除,并且将收到InterruptedException 。
                     *    中断标志位false导致无限循环
                     *
                     * 5、在catch块中,需要再次给中断标志位设置为true,2次调用停止程序才OK
                     */
                    Thread.currentThread().interrupt(); // 为什么要在异常处,再调用一次??
                    e.printStackTrace();
                }

                System.out.println("-----hello InterruptDemo3");
            }
        }, "t1");
        t1.start();

        //暂停几秒钟线程
        try { TimeUnit.SECONDS.sleep(1); } catch (InterruptedException e) { e.printStackTrace(); }

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

sleep() 方法抛出 InterruptedException 后,中断标识也被清空置为 false,我们在 catch 没有通过调用 interrupt() 方法再次将中断标识位设置位 true,这就是导致无限循环了

1.2.2 interrupted()

image-20220811164959692

public class InterruptDemo4 {
    public static void main(String[] args) {
        //测试当前线程是否被中断(检查中断标志),返回一个boolean并清除中断状态,
        // 第二次再调用时中断状态已经被清除,将返回一个false。
        System.out.println(Thread.currentThread().getName()+"\t"+Thread.interrupted()); // false
        System.out.println(Thread.currentThread().getName()+"\t"+Thread.interrupted()); // false
        Thread.currentThread().interrupt();// 中断标志位设置为true
        System.out.println(Thread.currentThread().getName()+"\t"+Thread.interrupted()); // true
        System.out.println(Thread.currentThread().getName()+"\t"+Thread.interrupted()); // false
    }
}

1.2.3 isInterrupted()

image-20220811165024422

1.3 使用中断标识停止线程

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() + "\t isStop 被修改为 true,程序终止");
                    break;
                }
                System.out.println("t1 ----hello volatile");
            }
        }, "t1").start();

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

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

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() + "\t isStop 被修改为 true,程序终止");
                    break;
                }
                System.out.println("T1 ----hello AtomicBoolean");
            }
        }, "T1").start();

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

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

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() + "\t isStop 被修改为 true,程序终止");
                    break;
                }
                System.out.println("t1 ----hello Thread API");
            }
        }, "T1");
        t1.start();

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

        new Thread(() -> t1.interrupt()).start();
    }
}

2、LockSupport

2.1 什么是 LockSupport

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

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

  • Permit 只有两个值,默认是 0(阻塞);1 是唤醒。需要注意的是许可的累积上限是 1

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

    image-20220811201510135

2.2 线程等待唤醒机制

2.2.1 Object 类中的方法

  • 要使用 wait()notify() 方法必须要在同步块或方法中,并成对出现
  • wait()notify() 方法使用顺序不能颠倒
public class LockSupportDemo1 {

    public static void main(String[] args) {
        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();
    }
}

2.2.2 Condition 类中的方法

await()notify() 类似于上面 wait()notify()

  1. Condition 中的线程等待和唤醒方法,需要先获取锁
  2. 一定要先 await()signal(),不能反了
public class LockSupportDemo2 {

    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() + "-- come in");
                condition.await();
                System.out.println(Thread.currentThread().getName() + "-- 被唤醒");
            } 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 {
                System.out.println(Thread.currentThread().getName() + "-- 进行唤醒 wake up");
                condition.signal();
            } catch (Exception e) {
                e.printStackTrace();
            } finally {
                lock.unlock();
            }
        }, "T2").start();
    }
}

2.2.3 LockSupport 类中的方法

  • 通过 park()unpark(thread) 方法来实现阻塞和唤醒线程的操作
  • LockSupport 和每个使用它的线程都有一个许可(permit)关联。当调用方法时:
    1. 如果有凭证,则会直接消耗掉这个凭证然后正常退出
    2. 如果无凭证,则会阻塞等待凭证可用
  • 每个线程都有一个相关的 permit, permit 最多只有一个, 重复调用 unpark() 也不会积累凭证
public class LockSupportDemo3 {

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

END

本文作者:
文章标题:LockSupport 与线程中断
本文地址:https://www.pendulumye.com/juc/496.html
版权说明:若无注明,本文皆PendulumYe原创,转载请保留文章出处。
最后修改:2022 年 08 月 28 日
千山万水总是情,给个一毛行不行💋