【Java】线程实例化 线程状态 线程属性
线程实例化
继承 Thread 类
-
创建类继承自 Thread 类 .
class MyThread extends Thread
-
重写
run()
方法 .@Overridepublic void run(){// 线程要执行的任务代码}
-
实例化自定义线程类 .
实现 Runnable 接口
-
创建类实现 Runnable 接口 .
class MyRunnable implements Runnable
-
实现
run()
方法 .@Overridepublic void run() {// 线程要执行的任务代码}
-
实例化传递实现类对象作为 Thread 构造函数的参数的对象 .
Thread thread = new Thread(new MyRunnable());
实现 Callable 和 Future 接口 ( FutureTask )
run()
方法没有返回值 .
-
创建类实现 Callable 接口 , 实现有返回值的
call()
方法 .class MyCallable implements Callable<V>{@Overridepublic V call() {// 线程要执行的任务代码} }
-
创建管理多线程运行结果的 Future 接口的实现类 FutureTask 的对象 , 构造方法传递实现类 MyCallable 的对象 .
FutureTask<V> ft = new FutureTask<>(new MyCallable());
-
创建 Thread 对象 , 构造方法传递 FutureTask 实现类的对象 .
方法
get
FutureTask 提供了两种重载 get 方法用于获取线程运行结果 :
V get()
:此方法会让当前线程阻塞 , 直到线程返回结果或在执行过程中抛出异常 .V get(long timeout, TimeUnit unit)
:该方法同样会使当前线程阻塞 , 不过会有超时限制 . 若在指定时间内任务未完成 , 就会抛出TimeoutException
异常 .
cancel
- 核心逻辑 :
FutureTask 的 cancel 方法会尝试对线程调用 interrupt 方法中断线程 , 所以 cancel 方法只是请求取消 , 如果线程执行任务本身没有处理中断的代码逻辑 , 那么即使调用 cancel 方法 , 线程也会正常运行 . - 参数 :
cancel 方法存在布氏值参数 mayInterruptIfRunning , 如果传入 true , 即表示当前线程处于 RUNNABLE 状态 , cancel 方法会尝试调用 interrupt 方法 , 如果传入 false , 只有在线程运行前 ( NEW 状态 ) 才能取消任务 . - 返回值 :
cancel 方法的返回值是是否请求取消成功的布尔值 , 即使线程执行任务没有处理中断的逻辑也会返回 true . - 旁支方法 :
isCanceled 方法顾名思义 , 不论线程是否结束运行 , 只要 FutureTask 对象成功调用了 cancel 方法 , 该方法就会返回 true .
isDone 方法返回线程任务是否完成 , 任务完成状态有三 : 正常结束运行 , 抛出异常终止 和 调用 cancel 方法取消 .
线程状态
Thread.State 枚举类定义
-
NEW 新建状态
通过 new 关键字实例化 Thread 对象后 , 未调用start()
方法前 . -
RUNNABLE 可运行状态
调用start()
方法后 , 线程处于可运行状态 .- Ready 就绪状态 : 线程获得除 CPU 时间片之外所有的必要资源后 , 获得 CPU 时间片之前 .
- Running 运行状态 : 获得 CPU 时间片后 , 执行
run()
代码时 .
CPU 时间片 也称为 调度量子 , 是操作系统用于管理多任务处理的一个概念 . 在多任务操作系统中 , 多个程序看似同时运行,但实际上一个CPU在同一时刻只能执行一个任务 . 为了实现多任务处理的假象 , 操作系统会将CPU的执行时间分割成一系列的小时间段,每个时间段就被称为 时间片 .
操作系统内核中的调度器会给每个正在运行的任务分配一个时间片 , 任务在这个时间片内获得 CPU 的使用权来执行 . 一旦当前任务的时间片用完 , 无论该任务是否已完成 , 调度器都会暂停当前任务 , 切换到等待任务 , 并给当前任务分配新的时间片来执行 . 通过快速地在不同任务之间切换 , 操作系统创造出多个程序同时运行的效果 .
-
BLOCKED 阻塞状态
线程尝试获取被其他线程占用的锁后 , 获得锁前 . -
WAITING 等待状态
线程调用Object.wait()
,Thread.join()
或LockSupport.park()
方法后会进入等待状态 . 处于等待状态的线程会无限期地等待 , 直到其他线程调用相应的唤醒方法 . -
TIMED_WAITING 定时等待状态
与等待状态类似 , 但定时等待状态的线程会在指定的时间后自动唤醒 . -
TERMINATED 终止状态
线程的run()
方法执行完毕 或 因异常意外结束后 . 终止状态的线程已经结束了生命周期 , 不能重新启动 .再次调用
start()
方法会抛出IllegalThreadStateException
异常 , 想达到类似重新启动的效果要重新实例化一个实现类相同的线程 .
线程属性
线程中断
线程中断是线程间通信 ( 控制线程的运行状态 ) 的早期手段 .
中断标志位
布尔类型 , 用于表示线程是否被请求中断 . 查询方法有两种 :
public boolean isInterrupted()
查询调用线程的中断状态 , 不清除中断标志位 : 即重置中断标志位为 false .public static boolean interrupted()
查询调用线程的中断状态 , 清除中断标志位 .
InterruptException
编译时异常 , 阻塞线程 ( 状态有 BLOCKED , WAITING , TIME_WAITING ) 被其他线程中断时抛出 , 因为如果不抛出异常 , 线程无法回到原本的运行状态 . 抛出异常后 , 中断标志位被清除 .
RUNNABLE 线程调用 interrupt()
会怎么样 ?
中断无效 , 线程中断方法只能设置线程的中断标志位为 true , 表示线程被请求中断 , 线程会继续执行代码直到主动检出中断标志位 .
作用
Java 的线程中断是协作式中断 , 不会直接停止线程 , 而是提供中断标志查询 , 后续怎么处理会交给线程自己 . 处于等待或者阻塞状态的线程被中断 , 只有抛出 ie 异常后才能转换成 RUNNABLE 状态继续执行后续逻辑 . 对于 RUNNABLE 状态只是打了个标记 , 后续怎么执行看是否要对中断标记进行处理 .
- 安全退出线程 , 在希望结束线程任务前自主控制资源释放 .
- 取消线程的阻塞状态 , 提供线程间通信的一种手段 .
如调整线程 2 为响应阻塞状态或睡眠足够长时间 , 在线程 1 执行完代码或到达关键代码后中断线程 2 , 强制线程 2 从睡眠中醒来 , 执行异常抛出代码后恢复 RUNNABLE 可运行状态 , 开始执行代码 .
线程守护
setDaemon(boolean on)
设置调用线程为守护状态 , 当所有的非守护线程结束时 , JVM 会自动退出 , 即使守护线程仍在运行 . 可以通过 isDaemon()
查询守护状态 , 新创建的线程默认是非守护线程 false .
Tips
- 必须在
start()
方法调用前设置守护状态 . - 守护线程的
finally
块可能不会执行 .
守护线程可以用来执行非关键的后台任务 , 如 JVM 垃圾回收 等 .
线程名
线程名与线程变量名是两个完全不同的概念 , 前者是线程的属性 , 可以存在于日志等记录中 ; 后者仅存在于源代码中 , 编译后没有实际意义 , 与线程本身无关 .
线程名可以通过 setName()
方法或在带参构造中传递 , 如果一个线程由无参构造实例化 , 其名称会按照 "Thread-" + n
的格式自动生成 , 其中 n 是由 JVM 内部维护的线程计数器决定的递增数字 :
private static synchronized int nextThreadNum() {return threadInitNumber++;
}
this.name = "Thread-" + nextThreadNum();
未捕获异常处理器
public interface Runnable {void run();
} // Runnable 接口的 run() 不能声明抛出任何受检异常.
throws 抛出异常是向上抛出 , 交给方法调用者来处理 ; catch 捕获是自行处理 , run()
作为通用任务接口 , 无法确定所有可能的异常类型 , 若允许抛出受检异常 , 多方调用者会被强制处理所有异常 , 导致代码冗余 .
线程异常通过 catch 捕获和未捕获异常处理器协作完成 . 未捕获异常处理器有 全局处理器 , 特定处理器 , 组处理器三种 , 通过 setDefaultUncaughtExceptionHandler()
方法设置 .
线程中的异常未被捕获时 , 线程的 UncaughtExceptionHandler
会被触发执行 , JVM 会调用该线程所设置的 UncaughtExceptionHandler
中的 uncaughtException
方法 , 并将抛出异常的线程对象和异常实例作为参数传递给该方法 , 从而执行在方法里所定义的异常处理逻辑 . 执行完处理器的处理代码后 , 线程会终止 .
如果线程组或全局的处理器管理的一个线程出现了未捕获异常 , 其他线程不会因为异常被连带终止 . 异常只会影响出现未捕获异常的线程 , 不会波及其他线程 .
当线程抛出一个未捕获异常时 , JVM 会按照 特定处理器 - 组处理器 - 全局处理器 的顺序查找 , 如果查找均失败则打印堆栈追踪并终止线程 .
Tips
-
处理器中抛出的异常不会传播到更外层 .
Thread.setUncaughtExceptionHandler((t, e) -> {System.out.println("处理原始异常: " + e.getMessage());throw new RuntimeException("处理器中的新异常"); // 这个异常会被忽略 });
-
对于线程池 , 要为每个任务单独处理异常 .
线程优先级
线程优先级用于表示线程执行的相对重要程度 , JVM 和 操作系统会依据线程的优先级安排线程调度 .
设置和范围
setPriority()
方法设置线程优先级 , getPriority()
方法获取线程的当前优先级 .
线程优先级取值范围是 1 到 10 , Thread.MIN_PRIORITY
= 1 最低优先级 , Thread.NORM_PRIORITY
= 5 默认优先级 , Thread.MAX_PRIORITY
= 10 最高优先级 .
缺陷
线程调度主要由操作系统负责 , 线程优先级依赖于操作系统 , 优先级值会映射到宿主机平台优先级 , 操作系统可能会根据线程的行为动态调整优先级 . 所以一般不用 , 了解即可 .
Thread.MIN_PRIORITY
= 1 最低优先级 , Thread.NORM_PRIORITY
= 5 默认优先级 , Thread.MAX_PRIORITY
= 10 最高优先级 .
缺陷
线程调度主要由操作系统负责 , 线程优先级依赖于操作系统 , 优先级值会映射到宿主机平台优先级 , 操作系统可能会根据线程的行为动态调整优先级 . 所以一般不用 , 了解即可 .