线程的状态与切换

news2024/11/24 20:09:46

文章目录

  • 线程的状态与切换
  • 一、线程的状态
    • 1、操作系统层面(5种)
    • 2、Java_API层面(6种)
  • 二、让出时间片 - yield
  • 三、线程插队 - join
    • 1、源码分析
    • 2、应用1 - 等待线程结果
    • 3、应用2 - 控制执行顺序
  • 四、计时等待 - sleep
  • 五、等待唤醒 - wait & notify
    • 1、相关API
    • 2、工作原理
    • 3、wait 和 sleep 的区别
    • 4、案例 - 基本使用
    • 5、案例 - if 还是 while
    • 6、案例 - 交替打印奇偶数
    • 7、案例 - 生产者消费者
  • 六、等待唤醒 - park & unpark
    • 1、相关API
    • 2、使用案例
    • 3、工作原理
    • 4、park 和 wait 的区别
  • 七、等待唤醒 - Condition
    • 1、相关API
    • 2、使用案例 - 顺序打印
  • 八、线程中断 - interrupt & stop
    • 1、中断线程的两种方式
      • 1)stop
      • 2)interrupt
    • 2、中断相关API
      • 1)interrupt
      • 2)isInterrupted
    • 3、打断正常运行的线程
    • 4、打断sleep、wait、join的线程
      • 1)打断 sleep
      • 2)打断 wait
      • 3)打断 join
    • 5、打断 park 的线程
    • 6、案例:两阶段中止模式
  • 九、线程状态与切换小结

线程的状态与切换

一、线程的状态

线程的状态指的是:线程在执行过程中所处的不同阶段。

1、操作系统层面(5种)

在这里插入图片描述

从操作系统层面,线程可分为 5种 状态:

  1. 初始状态

    仅仅是在语言层面上创建了线程对象,还未与操作系统线程关联。

  2. 可运行状态

    线程已创建,并且与操作系统相关联,可以由CPU调度执行。

    当被cpu分配了时间片,会从可运行状态 转换至 运行状态

  3. 运行状态

    线程获取了cpu时间片,正在运行。

    当cpu时间片用完,会从 运行状态 转换至 可运行状态,发生上下文切换。

  4. 阻塞状态

    如果调用了阻塞API,如BIO读写文件,线程就会进入阻塞状态

    如果调用了唤醒API,会由操作系统唤醒阻塞的线程,转换至 可运行状态

    可运行状态的区别是:任务调度器不会分配时间片给阻塞状态的线程

  5. 终止状态

    表示线程已经执行完毕,生命周期已经结束,不会再转换为其它状态。

2、Java_API层面(6种)

在这里插入图片描述

java.lang.Thread.State枚举类中,给出了6种线程状态:

  1. NEW - 新建

    线程对象刚被创建(new Thread()),还没有开始执行(调用start()方法)

  2. RUNNABLE - 可运行/就绪

    1. 可运行状态

      调用了start()方法,Java的线程对象就和操作系统的线程关联起来,CPU就可以调度线程执行run()方法。

    2. 运行状态

      线程获得了CPU时间片,真正开始执行run()方法。

    3. 阻塞状态

      调用了操作系统的阻塞API(注意:这里的阻塞是操作系统层面的,和 BLOCKED 不一样)

  3. BLOCKED - 阻塞

    当线程试图获取一个锁对象,而该对象锁被其他线程持有时,该线程会进入阻塞状态(获取到锁则变为可运行状态

  4. WAITING - 无限等待

    线程因为调用了不带超时参数的 wait()join() 方法而被挂起,进入无限等待状态

    必须等 其他线程调用notifynotifyAll方法唤醒 或 join的线程执行完,才会变为可运行状态

  5. TIMED_WAITING - 计时等待

    线程因为调用了带有超时参数的 wait()sleep() 或者 join() 方法而被挂起,进入计时等待状态

    这一状态会一直保持到 设置的时间到达 或 接收到唤醒通知notify/notifyAll

  6. TERMINATED - 死亡/终止

    线程 执行完其任务 或者 因异常退出而结束。

二、让出时间片 - yield

Thread类的静态方法 yield(),该方法会使当前线程让出CPU的使用权。

// 使当前线程主动让出当前CPU时间片,回到「可运行状态」,等待分配时间片。
public static native void yield();

yield() 方法的调用不会导致当前线程阻塞,它只是让当前线程暂停执行,转变为可运行状态。因此,执行完yield() 方法之后,两个线程可能会交替执行,也可能一个线程执行完了才轮到另一个线程执行,这取决于操作系统的调度策略

三、线程插队 - join

Thread类的方法join(),会使当前线程阻塞,等待调用join方法的线程运行(插队),可以控制线程执行顺序。

// 等待调用join方法的线程运行,直到该线程运行结束(默认millis=0)
public final void join() throws InterruptedException {
    join(0);
}

// 等待调用join方法的线程运行,最多等待n毫秒
public final synchronized void join(long millis) throws InterruptedException {...}

1、源码分析

public final synchronized void join(long millis) throws InterruptedException {
    
    // 开始时间
    long base = System.currentTimeMillis();
    
    // 经历的时间
    long now = 0;

    if (millis < 0) {
        throw new IllegalArgumentException("timeout value is negative");
    }

    if (millis == 0) {
        // 判断插队线程是否执行结束
        while (isAlive()) {
            wait(0);
        }
    } else {
        while (isAlive()) {
            // 剩余等待时间 = 最大等待时间 - 经历的时间
            long delay = millis - now;
            if (delay <= 0) {
                break;
            }
            // 此处不为 millis 是为了避免虚假唤醒导致多余等待
            wait(delay);
            now = System.currentTimeMillis() - base;
        }
    }
}

2、应用1 - 等待线程结果

public class GetResult {

    static int result = 0;

    public static void main(String[] args) throws InterruptedException {
        Thread t1 = new Thread(() -> {
            try {
                Thread.sleep(1000);
                result = 100;
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }, "t1");
        
        t1.start();
        
        // 等待t1线程执行结束再获取结果
        t1.join();
        
        System.out.println(result);
    }
}

3、应用2 - 控制执行顺序

现在有T1、T2、T3三个线程,怎样保证T2在T1执行完后执行,T3在T2执行完后执行

public class RunInOrder {
    public static void main(String[] args) {
        Thread t1 = new Thread(() -> {
            System.out.println("t1线程开始执行");
            for (int i = 0; i < 10; i++) {
                System.out.println("t1 ========> " + i);
            }
        });
        
        Thread t2 = new Thread(() -> {
            // t2线程执行时,t1线程插队
            try {
                t1.join();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            for (int i = 0; i < 10; i++) {
                System.out.println("t2 ========> " + i);
            }
        });
        
        Thread t3 = new Thread(() -> {
            // t3线程执行时,t2线程插队
            try {
                t2.join();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            for (int i = 0; i < 10; i++) {
                System.out.println("t3 ========> " + i);
            }
        });

        t1.start();
        t2.start();
        t3.start();
    }
}

四、计时等待 - sleep

Thread类的静态方法 sleep(),该方法会使当前线程进入计时等待状态指定毫秒。

// 使当前正在执行的线程以指定的毫秒数暂停,进入Timed Waiting状态
public static native void sleep(long millis) throws InterruptedException;

等待指定毫秒后,当前线程会自动唤醒,从计时等待状态进入可运行状态,等待分配时间片。

注意事项:

  • 任务调度器不会把时间片分配给【阻塞状态】的线程
  • 其它线程可以使用 interrupt 方法打断正在睡眠的线程,这时 sleep 方法会抛出 InterruptedException

五、等待唤醒 - wait & notify

1、相关API

在这里插入图片描述

  • wait 和 notify 是 Object 中的 等待唤醒方法,可以实现多个线程之间的协作。
  • 必须获得此对象的锁,才能调用这几个方法。(在 同步方法 或 同步代码块 中)
  • wait 方法会释放对象的锁,使线程进入 Monitor 的 WaitSet 等待区,从而让其他线程就机会获取对象的锁。
  • notify 方法的唤醒是随机的,如果 Monitor 的 WaitSet 等待区有多个线程,notify 会随机选择一个唤醒。(竞争锁)

2、工作原理

在这里插入图片描述

每个Java对象都可以关联一个Monitor,Monitor中同一时刻只能有一个Owner

  • 刚开始时 Monitor 中的 Owner 为 null

  • 当 Thread-2 执行 synchronized 代码块时,会将 Monitor 的所有者 Owner 设置为 Thread-2,上锁成功。

  • 当 Thread-2 占据锁时,如果 Thread-3,Thread-4 也来执行 synchronized 代码块,就会进入 EntryList中,变成 BLOCKED 状态

    BLOCKED 线程会在 Owner 线程释放锁时唤醒

  • Owner 线程调用 wait 方法,即可进入 WaitSet 变为 WAITING 状态。

    WAITING 线程会在 Owner 线程调用 notify 或 notifyAll 时唤醒,但唤醒后并不意味者立刻获得锁,仍需进入EntryList 重新竞争

3、wait 和 sleep 的区别

sleep()wait() 都是使线程暂停执行一段时间的方法,二者的区别为:

【工作原理】

  • sleep() 方法是Thread类的静态方法,是线程用来控制自身流程的
    • 它会使此线程暂停执行一段时间,而把执行机会让给其他线程,等到计时时间一到,此线程会自动苏醒。
  • wait()方法是Object类的方法,用于线程间的通信,它会使当前拥有对象锁的线程进入等待
    • 没有指定时间:直到其他线程用调用 notify()notifyAll() 时,线程才会苏醒。
    • 指定计时时间:被其他线程唤醒 或 等计时时间到了自动苏醒

【锁的处理】

  • sleep() 方法的主要作用是让线程暂停一段时间,让出CPU给其他线程,但是线程的监控状态依然保持着,时间一到则自动恢复,不涉及线程间的通信,因此调用sleep()方法不会释放锁
  • wait() 方法则不同,当调用wait()方法后,线程会释放掉它所占用的锁,进入等待状态,从而使线程所在对象中的其他synchronized 数据可以被别的线程使用。

【使用方面】

  • sleep()方法可以在任何地方使用,而 wait()方法必须在 同步方法同步代码块中 使用。
  • sleep()的过程中,有可能被其他对象调用它的interrupt(),产生InterruptedException异常。
  • 由于sleep()不会释放锁标志,容易导致死锁问题的发生。一般情况下,推荐使用wait()方法。

4、案例 - 基本使用

public class WaitAndNotifyTest {
    public static void main(String[] args) {

        Object lockObj = new Object();

        new Thread(() -> {
            synchronized (lockObj) {
                System.out.println("A线程获取了锁,准备wait");
                try {
                    lockObj.wait(); // 无限等待
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                System.out.println("A线程被唤醒,并且获取了锁");
            }
        }, "A线程").start();

        new Thread(() -> {
            String currentThread = Thread.currentThread().getName();

            // 确保A线程先获取锁
            try {
                Thread.sleep(2000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }

            synchronized (lockObj) {
                System.out.println("B线程获取了锁,准备唤醒A线程");
                lockObj.notify(); // 唤醒A线程
                System.out.println("B线程唤醒了A线程,还没有释放锁");
            }
            System.out.println("B线程释放了锁");
        }, "B线程").start();
    }
}

5、案例 - if 还是 while

class Number {
    private int num = 0;

    public synchronized void increment() throws InterruptedException {
        while (num == 0) {
            this.wait();
        }
        num++;
        System.out.println(Thread.currentThread().getName() + ":" + num);
        this.notifyAll();
    }
}

在多线程中,判断必须使用 while,使用 if 存在虚假唤醒问题,如下:

if (num == 0) {
    // 假设多个线程在这里wait
    // 如果使用if的话,线程被唤醒后,不会再次进行num == 0判断,这样就会导致num++多次。  
    // 如果使用while的话,线程被唤醒以后,会再次进行条件判断,则不会出现这样的问题。
    this.wait();
}
num++;

6、案例 - 交替打印奇偶数

/**
 * 使用 wait 和 notify 实现两个线程 交替打印1到100
 * 一个线程专门打印奇数 odd
 * 一个线程专门打印偶数 even
 */
public class PrintOddAndEven {

    public static void main(String[] args) {
        NumberMode numberMode = new NumberMode();
        new Thread(new PrintOdd(numberMode)).start(); 	// 奇数打印线程
        new Thread(new PrintEven(numberMode)).start(); 	// 偶数打印线程
    }

    static class NumberMode {
        public int num = 1;
    }

    // 奇数线程任务
    static class PrintOdd implements Runnable {

        NumberMode numberMode;

        public PrintOdd(NumberMode numberMode) {
            this.numberMode = numberMode;
        }

        @Override
        public void run() {
            while (numberMode.num < 100) {
                synchronized (numberMode) {
                    if (numberMode.num % 2 != 0) {
                        // 打印奇数
                        System.out.println("奇数:" + numberMode.num);
                        numberMode.num++;
                        numberMode.notify();
                    } else {
                        try {
                            System.out.println("奇数线程休息");
                            numberMode.wait();
                        } catch (InterruptedException e) {
                            e.printStackTrace();
                        }
                    }
                }
            }
        }
    }

    // 偶数线程任务
    static class PrintEven implements Runnable {

        NumberMode numberMode;

        public PrintEven(NumberMode numberMode) {
            this.numberMode = numberMode;
        }

        @Override
        public void run() {
            while (numberMode.num < 100) {
                synchronized (numberMode) {
                    if (numberMode.num % 2 == 0) {
                        // 打印偶数
                        System.out.println("偶数:" + numberMode.num);
                        numberMode.num++;
                        numberMode.notify();
                    } else {
                        try {
                            System.out.println("偶数线程休息");
                            numberMode.wait();
                        } catch (InterruptedException e) {
                            e.printStackTrace();
                        }
                    }
                }
            }
        }
    }
}

7、案例 - 生产者消费者

生产者消费者模式是并发编程中经典的模式。生产者和消费者通过分离的执行工作解耦,以不同的速度生产和消费数据。

生产者消费者模式的好处:

  1. 简化了开发,你可以独立的或并发的编写消费者和生产者,它仅仅只需知道共享对象是谁。
  2. 生产者不需要知道谁是消费者或者有多少消费者,对消费者来说也是一样。
  3. 生产者和消费者可以以不同的速度执行。
  4. 分离的消费者和生产者在功能上能写出更简洁、可读、易维护的代码。

【案例分析】

public class ProducerConsumerMode {

    public static void main(String[] args) {
        List<String> dish = new ArrayList<>();      // 盘子
        new Thread(new Producer(dish)).start();     // 生产者
        new Thread(new Consumer(dish)).start();     // 消费者
    }

    /**
     * 消费者:吃包子
     */
    static class Consumer implements Runnable {

        private final List<String> dish;

        public Consumer(List<String> dish) {
            this.dish = dish;
        }

        @Override
        public void run() {
            while (true) {
                synchronized (dish) {
                    // 如果盘子中没包子,等待,叫师傅做包子
                    if (dish.isEmpty()) {
                        try {
                            dish.wait();
                        } catch (InterruptedException e) {
                            e.printStackTrace();
                        }
                    }

                    String food = dish.remove(0);  // 从盘子中取出包子

                    try {
                        Thread.sleep(50);   //  吃包子的时间
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    System.out.println("吃货吃包子:" + food + " 盘子中剩余:" + dish.size());

                    //  通知师傅做包子
                    dish.notify();
                }
            }
        }
    }

    /**
     * 生产者:卖包子
     */
    static class Producer implements Runnable {

        private int count = 0;
        private final List<String> dish;

        public Producer(List<String> dish) {
            this.dish = dish;
        }

        @Override
        public void run() {
            // 生产包子的逻辑
            while (true) {
                // 生产者和消费者使用的是同一个集合,可以当做锁对象使用
                synchronized (dish) {
                    // 如果盘子中包子数量已达100
                    if (dish.size() >= 100) {
                        // 师傅休息,等待吃货吃包子
                        try {
                            dish.wait();
                        } catch (InterruptedException e) {
                            e.printStackTrace();
                        }
                    }

                    // 做包子
                    count++;

                    try {
                        // 模拟做包子的时间
                        Thread.sleep(50);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    dish.add("[" + count + "]"); //放到盘子中
                    System.out.println("包子铺师傅做包子:" + "[" + count + "] 盘子中还剩:" + dish.size());

                    dish.notify();  // 通知吃货去吃包子
                }
            }
        }
    }
}

六、等待唤醒 - park & unpark

1、相关API

park 和 unpark 是 JUC并发包中,LockSupport类的静态方法

public class LockSupport {
    
    private static final sun.misc.Unsafe UNSAFE;
    
    // 暂停当前线程
    public static void park() {
        UNSAFE.park(false, 0L);
    }

    // 恢复某个线程的运行
    public static void unpark(Thread thread) {
        if (thread != null)
            UNSAFE.unpark(thread);
    }
}
package sun.misc;

public final class Unsafe {
    public native void park(boolean isAbsolute, long time);
    public native void unpark(Object thread);
}

2、使用案例

/**
 * 先 park 再 unpark(park的时候会阻塞,然后unpark时被唤醒)
 */
public class ParkAndUnparkTest1 {
    public static void main(String[] args) throws InterruptedException {
        Thread t1 = new Thread(() -> {
            System.out.println("park...");
            LockSupport.park();    // 此处会暂停,然后被unpark唤醒
            System.out.println("resume...");
        }, "t1");

        t1.start();

        Thread.sleep(2000);

        System.out.println("unpark...");

        LockSupport.unpark(t1);
    }
}
/**
 * 先 unpark 再 park(这种情况,park的时候是不会停止的)
 */
public class ParkAndUnparkTest2 {
    public static void main(String[] args) throws InterruptedException {
        Thread t1 = new Thread(() -> {
            try {
                Thread.sleep(2000);
            } catch (InterruptedException e) {
                throw new RuntimeException(e);
            }
            System.out.println("park...");
            LockSupport.park();    // 此处不会暂停,会直接往下执行
            System.out.println("resume...");
        }, "t1");

        t1.start();

        Thread.sleep(1000);

        System.out.println("unpark...");

        LockSupport.unpark(t1);
    }
}

3、工作原理

每个线程都有自己的一个 Parker 对象,由三部分组成 _counter_cond_mutex

打个比喻,线程是旅行者,Parker是携带的背包, _cond 是休息的帐篷,_mutex 是帐篷的锁,_counter是干粮(0耗尽 1充足)

  • 调用 park ,就是要看需不需要停下来歇息
    • 如果备用干粮耗尽(_counter=0),那么钻进帐篷 _cond 歇息,并上锁 _mutex
    • 如果备用干粮充足(_counter=1),那么不需停留,继续前进
  • 调用 unpark,就好比补充干粮(_counter=1
    • 如果这时线程还在帐篷 _cond 休息,就唤醒让他继续前进
    • 如果这时线程还在运行,那么下次他调用 park 时,仅是消耗掉备用干粮(_counter=0),不需停留继续前进
    • 因为背包空间有限,多次调用 unpark 仅会补充一份备用干粮_counter 最大为1)

4、park 和 wait 的区别

  • wait 和 notify 是 Object 中的等待唤醒方法;park 和 unpark 是 JUC并发包 中 LockSupport 类的静态方法

  • wait 和 notify 必须在 同步方法 或 同步代码块 中使用;而 park 和 unpark 不必。

  • park 和 unpark 可以【阻塞】和【唤醒】指定的线程

    notify 是 随机唤醒一个等待线程,notifyAll 是唤醒所有等待线程。

  • park 和 unpark 可以先 unpark,而 wait 和 notify 不能先 notify

七、等待唤醒 - Condition

1、相关API

Condition 是 JUC包中的等待唤醒,通过和Lock的组合使用,实现精准的等待和唤醒

Condition 取代了对象监视器方法的使用

public interface Condition {
    // 等待
    void await() throws InterruptedException;
    void awaitUninterruptibly();
    long awaitNanos(long nanosTimeout) throws InterruptedException;
    boolean await(long time, TimeUnit unit) throws InterruptedException;
    boolean awaitUntil(Date deadline) throws InterruptedException;
    // 唤醒
    void signal();
    void signalAll();
}

Lock 取代了 synchornized 方法和语句

public interface Lock {
    // 获取监视器实例
    Condition newCondition();
}

2、使用案例 - 顺序打印

public class ConditionTest {
    public static void main(String[] args) {
        Print print = new Print();
        new Thread(() -> {for (int i = 0; i < 5; i++) print.printA();}, "A").start();
        new Thread(() -> {for (int i = 0; i < 5; i++) print.printB();}, "B").start();
        new Thread(() -> {for (int i = 0; i < 5; i++) print.printC();}, "C").start();
    }
}

class Print {

    private int number = 1;

    // Lock锁对象
    private ReentrantLock lock = new ReentrantLock();
    
    // Condition对象
    private Condition condition1 = lock.newCondition();
    private Condition condition2 = lock.newCondition();
    private Condition condition3 = lock.newCondition();

    public void printA() {
        // 1. 加锁
        lock.lock();

        try {
            // 2. 判断等待
            if (number != 1) {
                condition1.await();
            }
            // 3. 业务代码
            System.out.println(Thread.currentThread().getName() + "=> AAA");
            number = 2;
            // 4. 指定唤醒
            condition2.signal();
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            // 5. 解锁
            lock.unlock();
        }
    }

    public void printB() {
        lock.lock();
        try {
            if (number != 2) {
                condition2.await();
            }
            System.out.println(Thread.currentThread().getName() + "=> BBB");
            number = 3;
            condition3.signal();
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            lock.unlock();
        }
    }

    public void printC() {
        lock.lock();
        try {
            if (number != 3) {
                condition3.await();
            }
            System.out.println(Thread.currentThread().getName() + "=> CCC");
            number = 1;
            condition1.signal();
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            lock.unlock();
        }
    }
}

八、线程中断 - interrupt & stop

1、中断线程的两种方式

1)stop

之前的做法:Thread.currentThread().stop(); (由于安全问题已弃用)

  • 通过抛出ThreadDeathError来达到停止线程的目的,Error的抛出可能发生在程序的任何一个地方。
public class Thread implements Runnable {
    @Deprecated
    public final void stop() {
        // ...

        // The VM can handle all thread states
        stop0(new ThreadDeath());
    }

    private native void stop0(Object o);
}
public class ThreadDeath extends Error {
    private static final long serialVersionUID = -4417128565033088268L;
}

由于抛出ThreadDeatch异常,会导致该线程释放所持有的锁,而这种释放的时间点是不可控的,可能会导致出现线程安全问题和数据不一致情况,比如在同步代码块中在执行数据更新操作时线程被突然停止。

2)interrupt

现在的做法:Thread.currentThread().interrupt();

  • 将中断标记置为true,并不会使线程立即停止(可以通过isInterrupted判断中断状态,来使线程停止)
public class Thread implements Runnable {
    // 将中断标记置为true
    public void interrupt() {
        if (this != Thread.currentThread())
            checkAccess();

        synchronized (blockerLock) {
            Interruptible b = blocker;
            if (b != null) {
                interrupt0();           // Just to set the interrupt flag
                b.interrupt(this);
                return;
            }
        }
        interrupt0();
    }
}

2、中断相关API

每个线程都有一个中断标记(线程的一个属性),用来表明该线程是否被中断(默认为false)。

1)interrupt

interrupt() 方法,只是将中断标记置为true,并不会使线程立即停止(可以通过判断中断状态,来使线程停止)

public class Thread implements Runnable {
    // 将中断标记置为true
    public void interrupt() {
        if (this != Thread.currentThread())
            checkAccess();

        synchronized (blockerLock) {
            Interruptible b = blocker;
            if (b != null) {
                interrupt0();           // Just to set the interrupt flag
                b.interrupt(this);
                return;
            }
        }
        interrupt0();
    }
}

2)isInterrupted

判断线程是否中断(通过中断标记来判断)

public class Thread implements Runnable {
    // 判断线程是否中断 - 不会清除中断标记
    public boolean isInterrupted() {
        return isInterrupted(false);
    }

    // 判断线程是否中断 - 判断完毕,清除中断标记(置为false)
    public static boolean interrupted() {
        return currentThread().isInterrupted(true);
    }

    // 判断线程是否中断(参数表示是否清除中断标记 - 重置为false)  
    private native boolean isInterrupted(boolean ClearInterrupted); 
}

3、打断正常运行的线程

正常运行的线程,调用 interrupt() 方法,并不会停止,只会将 中断标记 置为 true。

public class ErrorInterruptRunning {
    public static void main(String[] args) throws InterruptedException {
        Thread t = new Thread(() -> {
            while (true) {
                System.out.println("while执行了");
            }
        });
        t.start();

        Thread.sleep(1000);

        // 线程t并不会停止 
        System.out.println("打断前:" + t.isInterrupted()); // false
        t.interrupt();
        System.out.println("打断后:" + t.isInterrupted()); // true
    }
}

可以通过isInterrupted判断中断状态,手动停止线程。

public class InterruptRunning {
    public static void main(String[] args) throws InterruptedException {
        Thread t = new Thread(() -> {
            while (true) {
                System.out.println("while执行了");
                // 通过`isInterrupted`判断中断状态,手动停止线程
                if(Thread.currentThread().isInterrupted()) {
                    System.out.println("被打断了, 退出循环");
                    break;
                }
            }
        });
        t.start();

        Thread.sleep(1000);

        System.out.println("打断前:" + t.isInterrupted()); // false
        t.interrupt();
        System.out.println("打断后:" + t.isInterrupted()); // true
    }
}

4、打断sleep、wait、join的线程

打断因为 sleepwaitjoin 这几个方法导致阻塞的线程,会:

  • 清除打断标记(将 打断标记 置为false
  • 抛出 InterruptedException 异常

1)打断 sleep

public class InterruptSleep {
    public static void main(String[] args) throws InterruptedException {
        Thread thread = new Thread(() -> {
            try {
                System.out.println("Thread is sleeping...");
                Thread.sleep(5000); // 线程休眠5秒钟
                System.out.println("Thread is awake.");
            } catch (InterruptedException e) {
                System.out.println("Thread was interrupted while sleeping.");
                System.out.println("interrupt flag:" + Thread.currentThread().isInterrupted());
            }
        });
        thread.start(); // 启动线程

        // 主线程等待一段时间,中断正在sleep的线程
        Thread.sleep(2000);
        thread.interrupt();
    }
}
Thread is sleeping...
Thread was interrupted while sleeping.
interrupt flag:false

2)打断 wait

public class InterruptWait {
    public static void main(String[] args) throws InterruptedException {
        Object lock = new Object();

        Thread thread = new Thread(() -> {
            synchronized (lock) {
                try {
                    System.out.println("Thread is waiting...");
                    lock.wait(); // 线程等待
                    System.out.println("Thread is awake.");
                } catch (InterruptedException e) {
                    System.out.println("Thread was interrupted while waiting.");
                    System.out.println("interrupt flag:" + Thread.currentThread().isInterrupted());
                }
            }
        });
        thread.start(); // 启动线程

        // 主线程等待一段时间,中断正在wait的线程
        Thread.sleep(2000);
        thread.interrupt();
    }
}
Thread is waiting...
Thread was interrupted while waiting.
interrupt flag:false

3)打断 join

public class InterruptJoin {
    public static void main(String[] args) throws InterruptedException {
        Thread thread1 = new Thread(() -> {
            try {
                System.out.println("Thread 1 is working...");
                Thread.sleep(3000); // 线程1休眠3秒钟
                System.out.println("Thread 1 is finished.");
            } catch (InterruptedException e) {
                System.out.println("Thread 1 was interrupted while sleeping.");
            }
        });

        Thread thread2 = new Thread(() -> {
            try {
                System.out.println("Thread 2 is working...");
                thread1.join(); // 等待线程1完成
                System.out.println("Thread 2 is finished.");
            } catch (InterruptedException e) {
                System.out.println("Thread 2 was interrupted while waiting for thread 1.");
                System.out.println("interrupt flag:" + Thread.currentThread().isInterrupted());
            }
        });

        thread1.start(); // 启动线程1
        thread2.start(); // 启动线程2

        // 主线程等待一段时间,中断被join阻塞的线程2
        Thread.sleep(2000);
        thread.interrupt();
    }

}
Thread 1 is working...
Thread 2 is working...
Thread 2 was interrupted while waiting for thread 1.
interrupt flag:false
Thread 1 is finished.

5、打断 park 的线程

  • 打断被park()阻塞的线程,会使线程从park()阻塞的地方继续向下执行。

  • 打断标记为true时,park()失效,调用park()线程不会阻塞。

    打断标记为false时,park()生效,调用park()线程会阻塞。

public class InterruptPark {

    public static void main(String[] args) throws InterruptedException {
        Thread t = new Thread(() -> {

            LockSupport.park();     // park()生效,阻塞在此处

            System.out.println("打断标记:" + Thread.currentThread().isInterrupted()); // true

            LockSupport.park();     // 打断标记为true时,park()失效,直接向下执行
            System.out.println("打断标记为true时,park()失效,不会阻塞");

            Thread.interrupted();  // 将打断标记置为false
            System.out.println("打断标记:" + Thread.currentThread().isInterrupted()); // false

            LockSupport.park();    // 打断标记为false,park()生效,阻塞在此处
            System.out.println("park()生效,执行不到这里");
        });

        t.start();

        // 主线程等待一段时间,中断被park()阻塞的线程
        Thread.sleep(2000);
        t.interrupt();
        System.out.println("线程被interrupt了");
    }
}

6、案例:两阶段中止模式

在一个线程T1中,如何优雅的中断线程T2(这里的优雅指给T2一个料理后事的机会,如释放资源)

public class TwoParseTerminationTest {
    public static void main(String[] args) throws InterruptedException {
        TwoParseTermination twoParseTermination = new TwoParseTermination();
        twoParseTermination.start();
        Thread.sleep(3000);  			
        twoParseTermination.stop();
    }
}

class TwoParseTermination {
    // 监控线程
    Thread monitorThread;
    
    // 启动监控线程
    public void start(){
        monitorThread = new Thread(()->{
            while(true) {
                // 判断是否被中止
                if (Thread.currentThread().isInterrupted()){
                    System.out.println("线程结束。。正在料理后事中");
                    break;
                }
                
                try {
                    Thread.sleep(500);
                    System.out.println("正在执行监控的功能");
                } catch (InterruptedException e) {
                    // sleep期间出现异常,会清除打断标记,因此需要重置打断标记为true
                    Thread.currentThread().interrupt();
                    e.printStackTrace();
                }
            }
        });
        monitorThread.start();
    }
    
    // 停止监控线程
    public void stop(){
        monitorThread.interrupt();
    }
}

九、线程状态与切换小结

在这里插入图片描述

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1452589.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

2024春节联欢晚会刘谦魔术分析

春晚已经越来越拉胯了&#xff0c;看着节目单没一个能打的&#xff0c;本来想说&#xff1a;办不起&#xff0c;就别办呗。 没想到第二天刘谦的魔术以一种很奇特的姿势火起来了&#xff0c;干脆蹭个热度&#xff0c;分析下魔术的原理。 魔术1 这个不算什么新奇的节目&#xf…

老师的“神秘武器”——教育战线的宝藏工具

每次考试成绩发布&#xff0c;是不是总让你头疼不已&#xff1f;面对一摞摞试卷&#xff0c;一个个需要手动输入的成绩&#xff0c;你是否也感到力不从心&#xff1f;别急&#xff0c;今天我就为大家揭秘老师们的“神秘武器”——那些在教育战线上&#xff0c;让老师们事半功倍…

代码随想录刷题笔记-Day18

1. 合并二叉树 617. 合并二叉树https://leetcode.cn/problems/merge-two-binary-trees/ 给你两棵二叉树&#xff1a; root1 和 root2 。 想象一下&#xff0c;当你将其中一棵覆盖到另一棵之上时&#xff0c;两棵树上的一些节点将会重叠&#xff08;而另一些不会&#xff09;…

【图像分割 2024】ParaTransCNN

【图像分割 2024】ParaTransCNN 论文题目&#xff1a;ParaTransCNN: Parallelized TransCNN Encoder for Medical Image Segmentation 中文题目&#xff1a;用于医学图像分割的并行TransCNN编码器 论文链接&#xff1a;https://arxiv.org/abs/2401.15307 论文代码&#xff1a;H…

机器学习西瓜书之决策树

目录 算法原理剪枝处理连续值处理缺失值处理多变量决策树 算法原理 从逻辑角度&#xff1a;通过一系列if-else语句进行多重判断&#xff0c;比如白富美的判断条件&#xff08;“白”“富”“美”&#xff09;。 从几何角度&#xff1a;根据定义的标准进行样本空间的划分。 以二…

应对DDoS攻击:快速恢复网站正常运行的关键步骤

当网站遭受DDoS&#xff08;分布式拒绝服务&#xff09;攻击时&#xff0c;可能会导致网站停机、性能下降和用户无法访问等问题&#xff0c;处理DDoS攻击需要采取一系列措施来应对和缓解攻击。 您的网站可能是今天的目标&#xff0c;因为面对DDoS&#xff08;分布式拒绝服务&am…

【Vue前端】vue使用笔记0基础到高手第2篇:Vue知识点介绍(附代码,已分享)

本系列文章md笔记&#xff08;已分享&#xff09;主要讨论vue相关知识。Vue.js是前端三大新框架&#xff1a;Angular.js、React.js、Vue.js之一&#xff0c;Vue.js目前的使用和关注程度在三大框架中稍微胜出&#xff0c;并且它的热度还在递增。Vue.js是一个轻巧、高性能、可组件…

【制作100个unity游戏之25】3D背包、库存、制作、快捷栏、存储系统、砍伐树木获取资源、随机战利品宝箱2(附带项目源码)

效果演示 文章目录 效果演示系列目录前言拖放、交换物品绘制拖拽物品插槽UI修改Inventory&#xff0c;控制拖放功能 源码完结 系列目录 前言 欢迎来到【制作100个Unity游戏】系列&#xff01;本系列将引导您一步步学习如何使用Unity开发各种类型的游戏。在这第25篇中&#xf…

C语言第二十六弹---字符串函数(下)

✨个人主页&#xff1a; 熬夜学编程的小林 &#x1f497;系列专栏&#xff1a; 【C语言详解】 【数据结构详解】 目录 1、strncat 函数的使用 2、strncmp 函数的使用 3、strstr 函数的使用和模拟实现 4、strtok 函数的使用 5、strerror 函数的使用 6、perror 函数的使用…

CSS的注释:以“ /* ”开头,以“ */ ”结尾

CSS的注释:以“ /* ”开头&#xff0c;以“*/”结尾 CSS的注释: 以“ /* ”开头&#xff0c;以“ */ ”结尾 在CSS中&#xff0c;注释是一种非常重要的工具&#xff0c;它们可以帮助开发者记录代码的功能、用法或其他重要信息。这些信息对于理解代码、维护代码以及与他人合作都…

【C++11】:unordered系列关联式容器

朋友们、伙计们&#xff0c;我们又见面了&#xff0c;本期来给大家解读一下有关unordered系列关联式容器的知识点&#xff0c;如果看完之后对你有一定的启发&#xff0c;那么请留下你的三连&#xff0c;祝大家心想事成&#xff01; C 语 言 专 栏&#xff1a;C语言&#xff1a;…

HarmonyOS一杯冰美式的时间 -- 验证码框

一、前言 像是短密码、验证码都有可能需要一个输入框&#xff0c;像是如下&#xff1a; 恰好在写HarmonyOS的时候也需要写一个验证码输入框&#xff0c;但是在实现的时候碰了几次灰&#xff0c;觉得有必要分享下&#xff0c;故有了此篇文章。 如果您有任何疑问、对文章写的不…

【MySQL】多表关系的基本学习

&#x1f308;个人主页: Aileen_0v0 &#x1f525;热门专栏: 华为鸿蒙系统学习|计算机网络|数据结构与算法 ​&#x1f4ab;个人格言:“没有罗马,那就自己创造罗马~” #mermaid-svg-3oES1ZdkKIklfKzq {font-family:"trebuchet ms",verdana,arial,sans-serif;font-siz…

WordPress作者页面链接的用户名自动变成16位字符串串插件Smart User Slug Hider

WordPress默认的作者页面URL链接地址格式为“你的域名/author/admin”&#xff0c;其中admin就是你的用户名&#xff0c;这样的话就会暴露我们的用户名。 为了解决这个问题&#xff0c;前面boke112百科跟大家分享了『如何将WordPress作者存档链接中的用户名改为昵称或ID』一文…

51单片机项目(31)——基于51单片机篮球计分器的proteus仿真

1.功能设计 可以通过两组按键&#xff0c;控制两个队伍的加减分&#xff0c;加分设置有&#xff0b;1分按键&#xff0c;&#xff0b;2分按键&#xff0c;&#xff0b;3分按键。减分设置有&#xff0d;1分按键。 设置有开始/暂停按键&#xff0c;按下开始&#xff0c;数码管便开…

人工智能学习与实训笔记(六):神经网络之智能推荐系统

人工智能专栏文章汇总&#xff1a;人工智能学习专栏文章汇总-CSDN博客 本篇目录 七、智能推荐系统处理 7.1 常用的推荐系统算法 7.2 如何实现推荐 7.3 基于飞桨实现的电影推荐模型 7.3.1 电影数据类型 7.3.2 数据处理 7.3.4 数据读取器 7.3.4 网络构建 7.3.4.1用户特…

老兵(11)

百度文心一格&#xff0c;大约是一年前上线并免费向用户开放的。其实也不是免费&#xff0c;而是“电量”比较好获得&#xff0c;白送的就16/每天&#xff0c;如果只是好奇玩玩的话也算够吧。 当时就很开心&#xff0c;因为一直想着把一些文案图像化&#xff0c;做成漫画的形式…

2024年【通信安全员ABC证】免费试题及通信安全员ABC证试题及解析

题库来源&#xff1a;安全生产模拟考试一点通公众号小程序 通信安全员ABC证免费试题根据新通信安全员ABC证考试大纲要求&#xff0c;安全生产模拟考试一点通将通信安全员ABC证模拟考试试题进行汇编&#xff0c;组成一套通信安全员ABC证全真模拟考试试题&#xff0c;学员可通过…

【plt.scatter绘制散点图】:从入门到精通,只需一篇文章!【Matplotlib】

【plt.scatter绘制散点图】&#xff1a;从入门到精通&#xff0c;只需一篇文章&#xff01;【Matplotlib】&#xff01;&#x1f680; 利用Matplotlib进行数据可视化示例 &#x1f335;文章目录&#x1f335; 一、plt.scatter入门&#xff1a;轻松迈出第一步 &#x1f463;二、…

代码随想录刷题笔记-Day17

1. 路径总和 112. 路径总和https://leetcode.cn/problems/path-sum/ 给你二叉树的根节点 root 和一个表示目标和的整数 targetSum 。判断该树中是否存在 根节点到叶子节点 的路径&#xff0c;这条路径上所有节点值相加等于目标和 targetSum 。如果存在&#xff0c;返回 true …