创建类继承自 Thread 类 .
class MyThread extends Thread
重写 run()
方法 .
@Override
public void run(){
// 线程要执行的任务代码
}
实例化自定义线程类 .
创建类实现 Runnable 接口 .
class MyRunnable implements Runnable
实现 run()
方法 .
@Override
public void run() {
// 线程要执行的任务代码
}
实例化传递实现类对象作为 Thread 构造函数的参数的对象 .
Thread thread = new Thread(new MyRunnable());
run()
方法没有返回值 .
创建类实现 Callable 接口 , 实现有返回值的 call()
方法 .
class MyCallable implements Callable<V>{
@Override
public 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
Thread.State 枚举类定义
NEW 新建状态
通过 new 关键字实例化 Thread 对象后 , 未调用 start()
方法前 .
RUNNABLE 可运行状态
调用 start()
方法后 , 线程处于可运行状态 .
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()
查询调用线程的中断状态 , 清除中断标志位 .编译时异常 , 阻塞线程 ( 状态有 BLOCKED , WAITING , TIME_WAITING ) 被其他线程中断时抛出 , 因为如果不抛出异常 , 线程无法回到原本的运行状态 . 抛出异常后 , 中断标志位被清除 .
interrupt()
会怎么样 ?中断无效 , 线程中断方法只能设置线程的中断标志位为 true , 表示线程被请求中断 , 线程会继续执行代码直到主动检出中断标志位 .
Java 的线程中断是协作式中断 , 不会直接停止线程 , 而是提供中断标志查询 , 后续怎么处理会交给线程自己 . 处于等待或者阻塞状态的线程被中断 , 只有抛出 ie 异常后才能转换成 RUNNABLE 状态继续执行后续逻辑 . 对于 RUNNABLE 状态只是打了个标记 , 后续怎么执行看是否要对中断标记进行处理 .
setDaemon(boolean on)
设置调用线程为守护状态 , 当所有的非守护线程结束时 , JVM 会自动退出 , 即使守护线程仍在运行 . 可以通过 isDaemon()
查询守护状态 , 新创建的线程默认是非守护线程 false .
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 会按照 特定处理器 - 组处理器 - 全局处理器 的顺序查找 , 如果查找均失败则打印堆栈追踪并终止线程 .
处理器中抛出的异常不会传播到更外层 .
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 最高优先级 .
线程调度主要由操作系统负责 , 线程优先级依赖于操作系统 , 优先级值会映射到宿主机平台优先级 , 操作系统可能会根据线程的行为动态调整优先级 . 所以一般不用 , 了解即可 .