概念

多线程概念

  • 进程(process):程序的运行实例,是程序向操作系统申请资源(比如内存空间和文件句柄)的基本单位;
  • 线程(Thread):
    • 进程中可独立执行的单位;
    • 一个进程可以包含多个线程;
    • 同一进程中的所有线程共享该进程中的资源,如内存空间,文件句柄等;
  • 线程分类:JVM用户线程与内核线程是1比1的关系
    • 用户线程:不需要操作系统管理,自行创建,优点是性能更好,缺点是无法充分利用多核CPU的优势,且多线程并不是真正意义上的多线程,而是受CPU内核时间片管理的影响
    • 内核线程:由操作系统管理的线程,能够充分利用多CPU的特点,缺点是线程切换耗时
  • 任务:线程要完成的计算就被称为任务,特定的线程总是在执行着特定的任务

串行、并行、并发

串行-并行-并发.png

  • 串行:多个任务依次执行
  • 并发(Concurrent):先做任务A,然后A准备就绪,开始等待;在等待A完成的时候,开始准备任务B,任务B准备就绪后,开始等待;在等待任务B完成时开始任务C的准备,然后等待3个任务的完成
  • 并行(Parallel):多任务同时进行,总耗时由需要时间最长的任务

编程范式

  • 函数式编程(Functional Programming):函数式基本抽象单位;
  • 面向对象编程:类是基本抽象单位;
  • 多线程编程:以线程为基本抽象单位的一种编程范式(Paradigm);

线程创建、启动与相关属性

线程创建

Java标准库类 java.lang.Thread 就是Java平台对线程的实现。Thread类或其子类的一个实例就是一个线程。

线程创建就是创建一个Thread类或其子类的实现,线程的任务逻辑放在Thread的run()方法中。Thread常用构造器:Thread() 与 Thread(Runnable target)

创建线程的三种方式:

1、继承 Thread 类

  1. public class Task extends Thread {
  2. @Override
  3. public void run() {
  4. System.out.println("任务执行!");
  5. }
  6. public static void main(String[] args) {
  7. Task task = new Task();
  8. task.start();
  9. }
  10. }

2、实现 Runnable 接口

  1. public class Task implements Runnable{
  2. @Override
  3. public void run() {
  4. System.out.println("任务执行!");
  5. }
  6. public static void main(String[] args) {
  7. Thread task = new Thread(new Task());
  8. task.start();
  9. }
  10. }

3、实现 Callable 接口

线程启动

通过 Thread 类的 start() 方法启动相应线程,实质是请求 java 虚拟机运行相应的线程,该线程何时运行由线程调度器 Scheduler(操作系统一部分)决定。start() 方法调用结束不意味相应线程开始运行

注意:

  • Thread.start():方法会调用本地方法,创建新线程执行 run() 方法
  • Thread.run():方法是在当前线程中执行 run() 方法,不会创建新线程【实现线程池的关键方法】

线程属性

常见的线程属性有如下几个:

  • 编号,ID
  • 名称,Name
  • 线程类别,Daemon
  • 优先级,Priority

1、线程ID:线程ID为 long 类型,用于标识不同的线程,该属性为只读属性

获取线程 ID 的方法:

  1. // 获取当前线程的ID
  2. long id = Thread.currentThread().getId();
  3. // 获取某个指定线程的ID
  4. Thread thread = new Thread();
  5. long id = thread.getId();

线程 ID 相关细节:

  • 某个编号的线程运行结束后,该编号可能会被后续创建的线程使用;
  • 线程编号只在 Java 虚拟机中的一次运行有效,即某个线程运行时的编号在 JVM 重启后有可能发生改变,因此线程 ID 不适合作为唯一标识

2、线程名称 name

  • String 类型,默认的格式为:”Thread-线程ID“,属于可以自定义的属性
  • 为了方便调试,尽量设置易懂的线程名 ```java // 在创建线程时设置线程name Thread thread = new Thread(“线程名”);

// 设置当前线程的线程名 Thread.currentThread().setName(“线程名”);

// 设置指定线程的线程名 Thread thread = new Thread(); thread.setName(“新的线程名”);

// 获取线程名 Thread.currentThread().getName();

  1. 3、线程类别 Daemon:类型 boolean,值为 true 表示相关线程为守护线程,否则表示相关线程为非守护线程,该属性的默认值与相应线程的父线程的该属性的值相同,该值可以自定义<br />注意事项:
  2. 1. 该属性必须在相应线程启动之前设置,即对 setDaemon 方法的调用必须在对 start 方法调用之前,否则 setDaemon 方法会抛出 IllegalThreadStateException 异常
  3. 1. 通过方法 Thread.currentThread().isDaemon() 可以获取当前线程是否为守护线程:
  4. - true:当前线程为守护线程(Daemon Thread)<br />
  5. - false:当前线程为用户线程(User Thread)<br />
  6. 3. 守护线程不会影响 Java 虚拟机的正常停止,一般用于执行一些重要性不是很高的任务(负责关键任务的线程不适合设置为守护线程),比如监视其他线程的运行情况;
  7. 3. **用户线程会阻止 Java 虚拟机的正常停止**,即一个Java虚拟机只有在其所有用户线程都运行结束(即 Thread.run() 调用未结束)的情况下才能正常停止;
  8. 3. Java虚拟机被强制停止时比如 Linux kill 命令,用户线程也无法阻止 Java 虚拟机的停止;
  9. 4、线程优先级 Priority:类型为int,本质是一个给线程调度器的提示,用于表示希望那个线程能够优先得到运行。Java定义了110个优先级,默认值为5,表示普通优先级
  10. ```java
  11. // 线程优先级常量
  12. public final static int MIN_PRIORITY = 1;
  13. public final static int NORM_PRIORITY = 5;
  14. public final static int MAX_PRIORITY = 10;
  15. // 设置线程优先级
  16. Thread.currentThread().setPriority(Thread.MAX_PRIORITY);
  17. // 获取当前线程的优先级
  18. int priority = Thread.currentThread().getPriority();

注意事项:

  • 不正确的优先级设置可能会产生线程饥饿
  • 优先级只是给线程调度器的提示信息,以便线程调度器决定优先调度哪个程序运行,并不能保证线程按照优先级给定的顺序运行

5、线程的层次关系(默认):

  • 子线程是否是守护线程取决于父线程;
  • 子线程的优先级为该线程的父线程优先级;

Thread 的常用方法

静态方法:
1、static Thread currentThread():返回当前线程,即当前代码的执行线程(对象)

Java 中任何一段代码总是执行在某个线程之中,执行当前代码的线程就被称为当前线程。Thread.currentThread() 可以返回当前线程,由于同一段代码可能被不同的线程执行,因此当前线程是相对的,即Thread.currentThread 的返回值在代码实际运行的时候可能对应着不同的线程(对象)。

  1. public class SubThread extends Thread {
  2. public SubThread() {
  3. // 输出 Thread.currentThread()
  4. System.out.println("Thread.currentThread():" +
  5. Thread.currentThread().getName());
  6. // 输出 this.getName()
  7. System.out.println("This.getName():"+this.getName());
  8. }
  9. @Override
  10. public void run() {
  11. // 输出 Thread.currentThread()
  12. System.out.println("Thread.currentThread():" +
  13. Thread.currentThread().getName());
  14. // 输出 this.getName()
  15. System.out.println("This.getName():" + this.getName());
  16. }
  17. public static void main(String[] args) {
  18. // 构造方法输出:
  19. // Thread.currentThread():main
  20. // This.getName():Thread-0
  21. final SubThread thread = new SubThread();
  22. thread.setName("测试线程");
  23. // 运行时输出:
  24. // Thread.currentThread():测试线程
  25. // This.getName():测试线程
  26. thread.start();
  27. // 使用Thread(Runnable) 实现线程
  28. Thread runnable = new Thread(thread);
  29. // 运行时输出:即this指针的是赋值后的线程对象
  30. // Thread.currentThread():Thread-1
  31. // This.getName():测试线程
  32. runnable.start();
  33. }
  34. }

2、static void sleep(long millis):是当前线程休眠(暂停运行)指定的时间

3、static void yield():使当前线程主动放弃其处理器的占用,这可能导致当前线程被暂停
注意:这个方法是不可靠的,该方法被调用时当前线程可能仍然继续运行(视系统的运行情况而定)

实例方法:

  1. void run():用于实现线程的任务处理逻辑,该方法一般由Java虚拟机直接调用,而不是应用程序调用
  2. void start():启动相应线程,该方法的返回并不代表相应的线程已经被启动,一个 Thread 实例的 start 方法只能被调用一次,多次调用会抛出异常
  3. void join():等待相应线程运行结束,即如果A线程调用B线程的join()方法,那么A线程的运行会被暂停,直到B运行结束
  4. isAlive():判断线程是否已经结束
    1. public static void main(String[] args) {
    2. final Thread threadA = new Thread(() -> {
    3. System.out.println("线程A开始执行");
    4. try {
    5. Thread.sleep(2000);
    6. } catch (InterruptedException e) {
    7. e.printStackTrace();
    8. }
    9. System.out.println("线程A执行完成");
    10. });
    11. final Thread threadB = new Thread(() -> {
    12. System.out.println("线程B正在执行,然后调用ThreadA的join()方法");
    13. try {
    14. threadA.join();
    15. } catch (InterruptedException e) {
    16. e.printStackTrace();
    17. }
    18. System.out.println("线程B再次执行");
    19. });
    20. threadB.start();
    21. threadA.start();
    22. }
    执行结果:
    image-20200908194555771.png

被废弃的方法:

  • stop:中止线程运行
  • suspend:暂停线程运行
  • resume:与suspend连用,使被 suspend 方法暂停的线程继续运行
  • destory:未实现的方法