目录💨

  • 1.线程(Thread)
  • 1.1 概念
  • 1.2 进程和线程的区别
  • 1.3 Java 的线程 和 操作系统线程 的关系
  • 2. 创建线程的方法
  • 2.1 继承 Thread 类
  • 2.2 实现 Runnable 接口
  • 2.3 其他变形写法
  • 3. Thread 类及常见方法
  • 3.1 Thread 的常见构造方法
  • 3.2 Thread 的几个常见属性
  • 4. Thread 中的一些重要方法
  • 4.1 start方法
  • 4.2 中断一个线程
  • 4.3 等待一个线程-join()
  • 4.4 获取当前线程引用
  • 4.5 休眠当前线程


1.线程(Thread)

1.1 概念

线程是什么:一个线程就是一个 “执行流”. 每个线程之间都可以按照顺讯执行自己的代码. 多个线程之间 “同时” 执行着多份代码.

为啥要有线程:“并发编程” 成为 “刚需”.

  • 单核 CPU 的发展遇到了瓶颈. 要想提高算力, 就需要多核 CPU. 而并发编程能更充分利用多核 CPU
    资源.
  • 有些任务场景需要 “等待 IO”, 为了让等待 IO 的时间能够去做一些其他的工作, 也需要用到并发编

其次, 虽然多进程也能实现 并发编程, 但是线程比进程更轻量3

  • 创建线程比创建进程更快.
  • 销毁线程比销毁进程更快.
  • 调度线程比调度进程更快.

1.2 进程和线程的区别

  • 进程是包含线程的. 每个进程至少有一个线程存在,即主线程。
  • 进程和进程之间不共享内存空间. 同一个进程的线程之间共享同一个内存空间.
  • 进程是系统分配资源的最小单位,线程是系统调度的最小单位。

1.3 Java 的线程 和 操作系统线程 的关系

线程是操作系统中的概念. 操作系统内核实现了线程这样的机制, 并且对用户层提供了一些 API 供用户使
用(例如 Linux 的 pthread 库).
Java 标准库中 Thread 类可以视为是对操作系统提供的 API 进行了进一步的抽象和封装.

2. 创建线程的方法

2.1 继承 Thread 类

  1. 继承 Thread 来创建一个线程类.
class MyThread extends Thread {
    @Override
    public void run() {
        System.out.println("这里是线程运行的代码");
   }
}
  1. 创建 MyThread 类的实例
MyThread t = new MyThread();
  1. 调用 start 方法启动线程
t.start(); // 线程开始运行

2.2 实现 Runnable 接口

  1. 实现 Runnable 接口
class MyRunnable implements Runnable {
    @Override
    public void run() {
        System.out.println("这里是线程运行的代码");
   }
}
  1. 创建 Thread 类实例, 调用 Thread 的构造方法时将 Runnable 对象作为 target 参数.
Thread t = new Thread(new MyRunnable());
  1. 调用 start 方法
t.start(); // 线程开始运行

对比上面两种方法:

  • 继承 Thread 类, 直接使用 this 就表示当前线程对象的引用.
  • 实现 Runnable 接口, this 表示的是 MyRunnable 的引用. 线程引用需要使用 Thread.currentThread()

2.3 其他变形写法

  • 匿名内部类创建 Thread 子类对象
// 使用匿名类创建 Thread 子类对象
Thread t1 = new Thread() {
    @Override
    public void run() {
        System.out.println("使用匿名类创建 Thread 子类对象");
   }
};
  • 匿名内部类创建 Runnable 子类对象
// 使用匿名类创建 Runnable 子类对象
Thread t2 = new Thread(new Runnable() {
    @Override
    public void run() {
        System.out.println("使用匿名类创建 Runnable 子类对象");
   }
});
  • lambda 表达式创建 Runnable 子类对象(常用)
// 使用 lambda 表达式创建 Runnable 子类对象
Thread t3 = new Thread(() -> System.out.println("使用匿名类创建 Thread 子类对象"));
Thread t4 = new Thread(() -> {
    System.out.println("使用匿名类创建 Thread 子类对象");
});

3. Thread 类及常见方法

Thread 类是 JVM 用来管理线程的一个类,换句话说,每个线程都有一个唯一的 Thread 对象与之关
联。用我们上面的例子来看,每个执行流,也需要有一个对象来描述,类似下图所示,而 Thread 类的对象就是用来描述一个线程执行流的,JVM 会将这些 Thread 对象组织起来,用于线程调度,线程管理。

3.1 Thread 的常见构造方法

java 多线程服务编排框架_java-ee

Thread t1 = new Thread();
Thread t2 = new Thread(new MyRunnable());
Thread t3 = new Thread("这是我的名字");
Thread t4 = new Thread(new MyRunnable(), "这是我的名字");

3.2 Thread 的几个常见属性

java 多线程服务编排框架_java_02

  • ID 是线程的唯一标识,不同线程不会重复
  • 名称是各种调试工具用到
  • 状态表示线程当前所处的一个情况
  • 优先级高的线程理论上来说更容易被调度到
  • 关于后台线程,需要记住一点:JVM会在一个进程的所有非后台线程结束后,才会结束运行。
    如果线程是后台线程,就不影响进程退出;如果是前台线程,就会影响到进程退出~
    创建的线程默认都是前台的线程,即使main方法执行完毕,进程也不能退出;得等前台线程执行完,整个进程才能退出
    如果是后台线程,main线程执行完毕,整个进程就直接退出了,此时这个后台线程就会被强制终止了;
  • 是否存活,即简单的理解,为 run 方法是否运行结束了
    如果调用start之后,run执行完之前,isAlive就返回TRUE
    如果调用start之前,run执行完之后,isAlive就返回FALSE
  • 线程的中断问题 :

4. Thread 中的一些重要方法

4.1 start方法

之前我们已经看到了如何通过覆写 run 方法创建一个线程对象,但线程对象被创建出来并不意味着线程
就开始运行了。

  • 覆写 run 方法是提供给线程要做的事情的指令清单
  • 线程对象可以认为是把 李四、王五叫过来了
  • 而调用 start() 方法,就是喊一声:”行动起来!“,线程才真正独立去执行了
  • 调用 start 方法, 才真的在操作系统的底层创建出一个线程.

run单纯的只是一个普通的方法,描述了任务的内容
start则是一个特殊的方法,内部会在系统中创建线程

4.2 中断一个线程

目前常见的有以下两种方式:

  1. 通过共享的标记来进行沟通
  2. 调用 interrupt() 方法来通知

示例-1: 使用自定义的变量来作为标志位.

  • 需要给标志位上加 volatile 关键字
public class ThreadDemo {
    private static class MyRunnable implements Runnable {
        public volatile boolean isQuit = false;
        @Override
        public void run() {
            while (!isQuit) {
                System.out.println(Thread.currentThread().getName()
                        + ": 别管我,我忙着转账呢!");
                try {
                    Thread.sleep(1000);
               } catch (InterruptedException e) {
                    e.printStackTrace();
               }
           }
            System.out.println(Thread.currentThread().getName()
                    + ": 啊!险些误了大事");
       }
   }
    public static void main(String[] args) throws InterruptedException {
        MyRunnable target = new MyRunnable();
        Thread thread = new Thread(target, "李四");
        System.out.println(Thread.currentThread().getName()
                + ": 让李四开始转账。");
        thread.start();
        Thread.sleep(10 * 1000);
        System.out.println(Thread.currentThread().getName()
                + ": 老板来电话了,得赶紧通知李四对方是个骗子!");
        target.isQuit = true; //此时得知对方是骗子 让线程终端
   }
}

java 多线程服务编排框架_System_03


示例-2: 使用 Thread.interrupted() 或者 Thread.currentThread().isInterrupted() 代替自定义标志位.

  • Thread 内部包含了一个 boolean 类型的变量作为线程是否被中断的标记
  • 使用 thread 对象的 interrupted() 方法通知线程结束.
public class ThreadDemo {
    private static class MyRunnable implements Runnable {
        @Override
        public void run() {
            // 两种方法均可以
            while (!Thread.interrupted()) {
            //while (!Thread.currentThread().isInterrupted()) {
                System.out.println(Thread.currentThread().getName()
                        + ": 别管我,我忙着转账呢!");
                try {
                    Thread.sleep(1000);
               } catch (InterruptedException e) {
                    e.printStackTrace();
                    System.out.println(Thread.currentThread().getName()
                            + ": 有内鬼,终止交易!");
                    // 注意此处的 break
                    break;
               }
           }
            System.out.println(Thread.currentThread().getName()
                    + ": 啊!险些误了大事");
       }
   }
    public static void main(String[] args) throws InterruptedException {
        MyRunnable target = new MyRunnable();
        Thread thread = new Thread(target, "李四");
        System.out.println(Thread.currentThread().getName()
                + ": 让李四开始转账。");
        thread.start();
        Thread.sleep(10 * 1000);
        System.out.println(Thread.currentThread().getName()
                + ": 老板来电话了,得赶紧通知李四对方是个骗子!");
        thread.interrupt();
   }
}

java 多线程服务编排框架_java_04


thread 收到通知的方式有两种:

  1. 如果线程因为调用 wait/join/sleep 等方法而阻塞挂起,则以 InterruptedException 异常的形式通知,清除中断标志
  • 当出现 InterruptedException 的时候, 要不要结束线程取决于 catch 中代码的写法. 可以选择
    忽略这个异常, 也可以跳出循环(break)结束线程.
  1. 否则,只是内部的一个中断标志被设置,thread 可以通过
  • Thread.interrupted() 判断当前线程的中断标志被设置,清除中断标志
  • Thread.currentThread().isInterrupted() 判断指定线程的中断标志被设置,不清除中断标志(常用)
    这种方式通知收到的更及时,即使线程正在 sleep 也可以马上收到。

标志位是否清除, 就类似于一个开关.

  • Thread.isInterrupted() 相当于按下开关, 开关自动弹起来了. 这个称为 “清除标志位”
  • 使用 Thread.isInterrupted() , 线程中断会清除标志位.

// 只有一开始是 true(标志位),后边都是 false(恢复默认),因为标志位被清除

java 多线程服务编排框架_intellij-idea_05

  • Thread.currentThread().isInterrupted() 相当于按下开关之后, 开关弹不起来, 这个称为"不清除标志位".
  • 使用 Thread.currentThread().isInterrupted() , 线程中断标记位不会清除.

4.3 等待一个线程-join()

有时,我们需要等待一个线程完成它的工作后,才能进行自己的下一步工作。
多个线程之间,调度顺序的不确定的,线程等待,就是其中一种,控制线程执行顺序的手段;主要是控制线程结束的先后顺序。

调用join时候,哪个线程调用join,哪个线程就会阻塞等待,等到对应的线程执行完毕为止(对应线程的run执行完)

列如:在main方法(main线程)里面t.join,是针对t这个线程对象调用的,此时就是让main线程等待t线程执行完。
调用join之后,main线程就会阻塞状态(暂时无法在CPU上执行)
需要注意的一点是:代码执行到join这一行,就暂时停下了,不继续往下执行了!等到t线程执行完毕(t的run方法跑完了),main线程才继续执行(恢复就绪状态);

通过线程等待,就是在控制让t先结束,main再结束:一定程度上的干预了这两个线程的执行顺序;

  • join操作默认情况下就是死等,这不合理
  • 我们应该给join传参,这个参数就是等待的时间:t.join(10000)(ms)
    进入join也会产生阻塞,但是这个阻塞不会一直下去;
    如果10s之内,t线程结束了,那么join直接返回
    如果10s之后,t线程还没有结束,此时join也会直接返回,不等了~

java 多线程服务编排框架_intellij-idea_06

4.4 获取当前线程引用

java 多线程服务编排框架_java 多线程服务编排框架_07

java 多线程服务编排框架_java_08


main方法里面的就是main线程:

java 多线程服务编排框架_intellij-idea_09

4.5 休眠当前线程

有一点要记得,因为线程的调度是不可控的,所以,这个方法只能保证实际休眠时间是大于等于参数设置的休眠时间的。

java 多线程服务编排框架_java 多线程服务编排框架_10

java 多线程服务编排框架_java-ee_11


这里的意思是让main线程休眠3000ms(因为线程的调度是不可控,这里并不是精确的等了3000ms,而是大于3000ms):

java 多线程服务编排框架_java-ee_12

  • 如果某个线程调用了sleep方法,这个线程对应的pcb就会进入到阻塞队列(操作系统调度线程的时候,就只是从就绪队列中挑选合适的pcb到CPU上运行,阻塞队列里面的pcb就只能干等着…),当睡眠时间到了,系统就会把刚才这个pcb从阻塞队列挪回到就绪队列(针对Linux来说,Windows不是开源的,我们无从得知系统内部是怎么进行线程调度的,但是推测和Linux差别不会太大!)
  • over ~ 🎈