Thread类用于操作线程,是一个实现多线程的基础类,本文主要针对Thread类代码进行拆分解读
Thread类实现的接口
class Thread implements Runnable {
/* What will be run.意思就是线程具体执行内容放在run里 */
private Runnable target;
@Override
public void run() {
if (target != null) {
target.run();
}
}
}
Runnable接口只定义了一个方法run,重写Thread类中的run方法,将需要被多线程执行的代码存储到该run方法当中
Thread类定义涉及的属性
1.本地注册
private static native void registerNatives();
//静态代码块:在类加载时调用 registerNatives(),确保本地方法在类初始化时完成注册
static {
registerNatives();
}
// 其他本地方法
public final native Class<?> getClass();
public native int hashCode();
protected native Object clone() throws CloneNotSupportedException;
线程的创建、调度、中断等操作需要直接调用操作系统 的API,线程操作频繁,本地方法调用频率高,Thread类在创建时候静态初始化本地注册,避免动态查找开销。可以减少 JVM 的开销,提升性能
2.线程优先级
private int priority;
//线程优先级最高为10,最低为1,默认为5
public final static int MIN_PRIORITY = 1;
public final static int NORM_PRIORITY = 5;
public final static int MAX_PRIORITY = 10;
public final void setPriority(int newPriority) {
ThreadGroup g;
checkAccess();
if (newPriority > MAX_PRIORITY || newPriority < MIN_PRIORITY) {
throw new IllegalArgumentException();
}
if((g = getThreadGroup()) != null) {
if (newPriority > g.getMaxPriority()) {
newPriority = g.getMaxPriority();
}
setPriority0(priority = newPriority);
}
}
// 获取优先级方法
public final int getPriority() {
return priority;
}
线程执行有优先级,优先级越高先执行机会越大(并不是一定先执行)
3.线程的状态
Thread对象共有6种状态:NEW(新建),RUNNABLE(运行),BLOCKED(阻塞),WAITING(等待),TIMED_WAITING(有时间的等待),TERMINATED(终止)
public enum State {
NEW,
RUNNABLE,
BLOCKED,
WAITING,
TIMED_WAITING,
TERMINATED;
}
public State getState() {
return sun.misc.VM.toThreadState(threadStatus);
}
Thread类构造器
Thread类一共有9个构造函数 初始化init构造对象
其中一个比较特殊构造函数是 Thread(Runnable target, AccessControlContext acc) (访问控制上下文)直接调用的最底层的初始化init方法,而其余8个构造器实现初始化的init方法底层实现的方法这个,只是参数AccessControlContext acc为null
init方法解读
//ThreadGroup 指定当前线程的线程组,未指定时线程组为创建该线程所属的线程组
//Runnable target 指定运行其中的Runnable,一般都需要指定,不指定的线程没有意义,或者通过创建Thread的子类并重新run方法
//String name 线程的名称,不指定则自动生成:"Thread-" + nextThreadNum()
//long stackSize 预期堆栈大小,不指定默认为0,0代表忽略这个属性
//AccessControlContext acc参数用于设置线程的访问控制上下文。如果acc为null,则调用AccessController.getContext()获取当前的访问控制上下文;如果acc不为null,则使用传入的acc作为线程的访问控制上下文
// boolean inheritThreadLocals 用于指定新创建的线程是否应该继承父线程的 InheritableThreadLocal 变量
private void init(ThreadGroup g, Runnable target, String name,
long stackSize, AccessControlContext acc,
boolean inheritThreadLocals) {
// 设置线程名
if (name == null) {
throw new NullPointerException("name cannot be null");
}
this.name = name;
// 获取当前线程 并赋值为父线程 Thread parent
Thread parent = currentThread();
// 设置线程组
this.group = g == null ? parent.getThreadGroup() : g;
// 设置是否为守护线程(默认与父线程相同)
this.daemon = parent.isDaemon();
// 设置线程优先级(默认与父线程相同)
this.priority = parent.getPriority();
// 设置类加载器和上下文 ClassLoader
if (acc == null) {
this.contextClassLoader = parent.getContextClassLoader();
} else {
this.contextClassLoader = null;
}
// 设置目标 Runnable
this.target = target;
// 设置栈大小
this.stackSize = stackSize;
// 分配线程 ID
this.tid = nextThreadID();
// 继承父线程的 ThreadLocal 值
if (inheritThreadLocals && parent.inheritableThreadLocals != null) {
this.inheritableThreadLocals = ThreadLocal.createInheritedMap(parent.inheritableThreadLocals);
}
// 设置线程的 AccessControlContext
this.inheritedAccessControlContext = acc != null ? acc : AccessController.getContext();
}
Thread类Public方法
Thread Thread.currentThread() :获得当前线程的引用。获得当前线程后对其进行操作。
Thread.UncaughtExceptionHandler getDefaultUncaughtExceptionHandler() :返回线程由于未捕获到异常而突然终止时调用的默认处理程序。
int Thread.activeCount():当前线程所在线程组中活动线程的数目。
void dumpStack() :将当前线程的堆栈跟踪打印至标准错误流。
int enumerate(Thread[] tarray) :将当前线程的线程组及其子组中的每一个活动线程复制到指定的数组中。
Map<Thread,StackTraceElement[]> getAllStackTraces() :返回所有活动线程的堆栈跟踪的一个映射。
boolean holdsLock(Object obj) :当且仅当当前线程在指定的对象上保持监视器锁时,才返回 true。
boolean interrupted() :测试当前线程是否已经中断。
void setDefaultUncaughtExceptionHandler(Thread.UncaughtExceptionHandler eh) :设置当线程由于未捕获到异常而突然终止,并且没有为该线程定义其他处理程序时所调用的默认处理程序。
void sleep(long millis) :休眠指定时间
void sleep(long millis, int nanos) :休眠指定时间
void yield() :暂停当前正在执行的线程对象,并执行其他线程。意义不太大
void checkAccess() :判定当前运行的线程是否有权修改该线程。
ClassLoader getContextClassLoader() :返回该线程的上下文 ClassLoader。
long getId() :返回该线程的标识符。
String getName() :返回该线程的名称。
int getPriority() :返回线程的优先级。
StackTraceElement[] getStackTrace() :返回一个表示该线程堆栈转储的堆栈跟踪元素数组。
Thread.State getState() :返回该线程的状态。
ThreadGroup getThreadGroup() :返回该线程所属的线程组。
Thread.UncaughtExceptionHandler getUncaughtExceptionHandler() :返回该线程由于未捕获到异常而突然终止时调用的处理程序。
void interrupt() :中断线程。
boolean isAlive() :测试线程是否处于活动状态。
boolean isDaemon() :测试该线程是否为守护线程。
boolean isInterrupted():测试线程是否已经中断。
void join() :等待该线程终止。
void join(long millis) :等待该线程终止的时间最长为 millis 毫秒。
void join(long millis, int nanos) :等待该线程终止的时间最长为 millis 毫秒 + nanos 纳秒。
void run() :线程启动后执行的方法。
void setContextClassLoader(ClassLoader cl) :设置该线程的上下文 ClassLoader。
void setDaemon(boolean on) :将该线程标记为守护线程或用户线程。
void start():使该线程开始执行;Java 虚拟机调用该线程的 run 方法。
String toString():返回该线程的字符串表示形式,包括线程名称、优先级和线程组。
详情介绍几个方法
1.Thread currentThread()
取的是当前线程。
该方法是本地静态方法,用于获取当前线程,返回线程对象
public static native Thread currentThread();
2.void start()
public synchronized void start() {
// 检查线程状态,确保线程未启动,threadStatus 初始化为0 代表线程未启动
if (threadStatus != 0)
throw new IllegalThreadStateException();
// 将线程添加到线程组
group.add(this);
// 标记线程已启动
started = false;
try {
// 调用本地方法 start0(),启动线程
start0();
started = true;
} finally {
try {
if (!started) {
// 如果启动失败,从线程组中移除线程
group.threadStartFailed(this);
}
} catch (Throwable ignore) {
// 忽略异常
}
}
}
执行start方法,把这个线程加入到线程组里面去在启动,这也是为什么线程先进行start方法,然后才是run方法
3.run方法
是线程执行任务的主要代码
public void run() {
//target:private Runnable target;
// 是实现Runnable接口的重写,但具体执行的内容还是具体实现类继承Thread类重写的run方法实现
if (target != null) {//private Runnable target;
target.run();
}
}
4.void sleep(long millis) & void sleep(long millis, int nanos)
这两个方法是让线程休眠
- sleep(millis)是本地方法,让当前线程休眠指定时间,sleep不释放锁,sleep() 是 Java 的本地方法,其底层实现依赖于操作系统的线程调度,线程的锁状态由 JVM 管理,操作系统不会干预锁的释放
public static native void sleep(long millis) throws InterruptedException;
这里说明下该方法为什么会throws InterruptedException,
sleep()、wait()、join()都属于阻塞方法,三个方法在执行过程中,会不断地检查当前线程的中断状态
①如果发现中断状态已经被设置(通常是通过调用interrupt()方法),它们就会抛出InterruptedException异常,并清除中断状态(就是退出等 睡这些 不再等待 不再沉睡),开始执行catch(InterruptedException)里面的代码内容,因为抛出的异常捕捉到了,然后继续执行线程内容
②如果在调用这些方法之前没有设置中断状态,或者在调用过程中中断状态被清除,那么就不会抛出异常InterruptedException
- void sleep(long millis, int nanos)只是将nanos(纳秒)转为millis(毫米)(四舍五入转换),1毫秒等于1,000,000纳秒,调用sleep(millis)方法
public static void sleep(long millis, int nanos)
throws InterruptedException {
if (millis < 0) {
throw new IllegalArgumentException("timeout value is negative");
}
if (nanos < 0 || nanos > 999999) {
throw new IllegalArgumentException(
"nanosecond timeout value out of range");
}
if (nanos >= 500000 || (nanos != 0 && millis == 0)) {
millis++;
}
sleep(millis);
}
5.void join() & void join(long millis) & void join(long millis, int nanos)
- 无限等待:join() 调用 join(0),进入无限等待逻辑
public final void join() throws InterruptedException {
join(0); // 调用 join(long millis) 方法,参数为 0
}
这里说明下该方法为什么会throws InterruptedException,
sleep()、wait()、join()都属于阻塞方法,三个方法在执行过程中,会不断地检查当前线程的中断状态
①如果发现中断状态已经被设置(通常是通过调用interrupt()方法),它们就会抛出InterruptedException异常,并清除中断状态(就是退出等 睡这些 不再等待 不再沉睡),开始执行catch(InterruptedException)里面的代码内容,因为抛出的异常捕捉到了,然后继续执行线程内容
②如果在调用这些方法之前没有设置中断状态,或者在调用过程中中断状态被清除,那么就不会抛出异常InterruptedException
- 超时等待:void join(long millis) 根据 millis 的值,进入无限等待或超时等待逻辑
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); // 调用 wait(0),进入无限等待
}
} else { // 超时等待
while (isAlive()) { // 检查目标线程是否存活
long delay = millis - now; // 计算剩余等待时间
if (delay <= 0) { // 超时
break;
}
wait(delay); // 调用 wait(delay),进入超时等待
now = System.currentTimeMillis() - base; // 更新已等待时间
}
}
}
从上面代码看 join()方法实现的底层代码是 wait(delay);所以join方法会释放锁
- 精确超时等待:void join(long millis, int nanos) 处理纳秒部分后,调用 join(long millis)
public final synchronized void join(long millis, int nanos) throws InterruptedException {
if (millis < 0) {
throw new IllegalArgumentException("timeout value is negative");
}
if (nanos < 0 || nanos > 999999) {
throw new IllegalArgumentException("nanosecond timeout value out of range");
}
if (nanos >= 500000 || (nanos != 0 && millis == 0)) {
millis++; // 纳秒部分超过 500000 时,增加 1 毫秒
}
join(millis); // 调用 join(long millis) 方法
}
总结:
①void join() & void join(long millis) & void join(long millis, int nanos) 本质上都是join(long millis) 方法
②当前线程会获取目标线程的对象锁,其底层实现是Object的wait 方法,释放锁并进入等待状态
③实现join之后进入等待状态的线程,是根据notifyAll()方法醒来然后重新去竞争锁,其实现是在Thread类的exit()方法,exit() 方法:在线程终止时被调用,负责释放资源和唤醒等待的线程
简化版本代码示例
private void exit() {
if (group != null) {
group.threadTerminated(this);
group = null;
}
synchronized (this) {
// 线程终止时,调用 notifyAll() 唤醒所有等待的线程
if (nthreads == 0) {
notifyAll();
}
}
}
6.boolean interrupted() & void interrupt() & boolean isInterrupted()
中断:CPU暂时中止程序的执行转而处理这个新的情况的过程就叫做中断
线程中断:即线程运行过程中被其他线程给打断了。会报出InterruptedException(中断异常),这个异常比较特殊,属于一个检查性质的异常,异常出现了,需要我们自行去检查并做出后续的处理,线程是死亡、还是等待新的任务或是继续运行至下一步,都是我们检查到这个异常并自己决定后续怎么处理。它并不像stop方法那样会停止一个正在运行的线程
需要明确的是,InterruptedException只有在以下情况才会被抛出:
1. 线程在调用sleep()、wait()、join()等方法时处于阻塞状态
2. 另一个线程调用了该线程的interrupt()方法,中断了该线程
代码示例
Thread thread = new Thread(() -> {
try {
//sleep方法属于阻塞方法之一
Thread.sleep(1000);
System.out.println("睡了一秒");
} catch (InterruptedException e) {
// 代码不会执行到这里, 因为没有触发InterruptedException中断异常
//catch自然捕捉不到也自然不会走到这里
System.out.println("这里打印不出来了,代码不会走到这了");
}
});
thread.start();
// 如果线程里使用了sleep join wait 等阻塞方法,如果不调用thread.interrupt(),线程未被中不会触发InterruptedException中断异常,结果就是该睡的睡一会,该等的等一下,然后时间到了线程在进行下去,如果调用了thread.interrupt()方法 就会中断 睡 等 这些操作 直接进行下去(一般直接进入catch里面)
正确的写法应该是
Thread thread = new Thread(() -> {
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
System.out.println("线程被中断!");
//子线程里的重新设置中断标识位以便实际场景后续需要,这个示例中可以不用
Thread.currentThread().interrupt();
}
});
thread.start();
// 正确:显式中断线程
thread.interrupt(); // ✅ 触发InterruptedException中断,让子线程退出阻塞状态或响应中断请求
这三个方法是的针对线程中断标记,从而操作,中断标志位默认是false。线程的中断标志位是一个布尔值,表示线程是否被请求中断
- void interrupt() :设置中断标志位,中断阻塞操作
public void interrupt() {
if (this != Thread.currentThread()) {
checkAccess(); // 检查当前线程是否有权限中断目标线程
}
synchronized (blockerLock) {
Interruptible b = blocker;
if (b != null) {
interrupt0(); // 设置中断标志位
b.interrupt(this); // 中断阻塞操作(如 I/O 操作)
return;
}
}
interrupt0(); // 是一个本地方法,由 JVM 实现,设置中断标志位 true
}
注意:interrupt()方法 重新设置中断标识位一定要在合适的时机,线程已终止或未运行在使用interrupt()都是无效的
Thread thread = new Thread(() -> {
// 线程很快执行完毕
});
thread.start();
// 等待线程结束后再中断
Thread.sleep(2000);
thread.interrupt(); // ❌ 线程已终止,中断无效
简单来说中断标识设置一定要在阻塞方法或者别的中断异常出现之前就设置,这样中断异常来了才能处理,下面这个示例不规范但是好理解
@Test
public void run() {
try {
//如果这里不使用.interrupt()方法重新设置中断标识为true,代码走到sleep也就是睡了1秒 然后继续下去,没有中断睡的操作,如果在sleep之前使用了 那就会中断睡的操作,从sleep中退出来,然后被catch到,然后执行catch里面的内容,然后线程在继续走下去(如果线程还有别的执行内容)
Thread.currentThread().interrupt();
Thread.sleep(1000);
System.out.println("睡了一秒");
} catch (InterruptedException e) {
// 恢复中断标志
System.out.println("中断异常被捕获,也恢复中断状态");
}
}
- boolean isInterrupted() :检查中断标志位状态 只是判断不做改变
public boolean isInterrupted() {
return isInterrupted(false); // 调用重载方法,传参flase 表示不清除中断标志位
}
//isInterrupted(false) 是一个本地方法 isInterrupted(false):表示不清除中断标志位
private native boolean isInterrupted(boolean ClearInterrupted);
- boolean interrupted() :检查并清除当前线程的中断标志位,会返回当前线程的中断状态,并将当前线程的中断状态设为false
public static boolean interrupted() {
return currentThread().isInterrupted(true);// 调用重载方法,传参true清除中断标志位
}
这三个方法的实现依赖两个本地方法
private native void interrupt0();
private native boolean isInterrupted(boolean ClearInterrupted);
interrupt0() 的本地方法实现通常如下简化版本
JNIEXPORT void JNICALL Java_Thread_interrupt0(JNIEnv *env, jobject obj) {
Thread* thread = getThreadFromObject(env, obj);
thread->setInterrupted(true); // 设置中断标志位
}
isInterrupted(boolean ClearInterrupted) 的本地方法实现通常如下 简化版本
JNIEXPORT jboolean JNICALL Java_Thread_isInterrupted(JNIEnv *env, jobject obj, jboolean ClearInterrupted) {
Thread* thread = getThreadFromObject(env, obj);
jboolean interrupted = thread->isInterrupted(); // 获取中断标志位状态
if (ClearInterrupted) {
thread->setInterrupted(false); // 清除中断标志位
}
return interrupted;
}
这三个方法的使用简单代码示例如下
public static void main(String[] args) {
Thread thread = Thread.currentThread();
System.out.println(thread.getName() + "---第一次判断当前线程是否被进行中断标记,结果为:" + thread.isInterrupted());
if (!thread.isInterrupted()) {
//进行中断标记
System.out.println(thread.getName() + "---进行中断标记操作");
thread.interrupt();
}
System.out.println(thread.getName() + "---第二次判断当前线程是否被进行中断标记,结果为:" + thread.isInterrupted());
//第三次进行判断
System.out.println(thread.getName() + "---第三次判断当前线程是否被进行中断标记,结果为:" + thread.isInterrupted());
System.out.println("当前线程的中断标记状态为:" + Thread.interrupted());
//第四次进行判断
System.out.println(thread.getName() + "---第四次判断当前线程是否被进行中断标记,结果为:" + thread.isInterrupted());
}
打印台输出
main---第一次判断当前线程是否被进行中断标记,结果为:false
main---进行中断标记操作
main---第二次判断当前线程是否被进行中断标记,结果为:true
main---第三次判断当前线程是否被进行中断标记,结果为:true
当前线程的中断标记状态为:true
main---第四次判断当前线程是否被进行中断标记,结果为:false
测试中断f方法interrupt(),线程优雅停止
@Test
public void test() throws InterruptedException {
Thread worker = new Thread(() -> {
while (!Thread.currentThread().isInterrupted()) {
System.out.println("线程 Working...");
try {
Thread.sleep(500); // 模拟工作
} catch (InterruptedException e) {
System.out.println("Thread interrupted while sleeping");
Thread.currentThread().interrupt(); // 重新设置中断标志位
}
}
System.out.println("Thread stopped gracefully");
});
worker.start();
Thread.sleep(2000); // 主线程等待 2 秒
worker.interrupt(); // 请求停止 worker 线程
}
执行顺序是:
(1) 主线程启动
1.创建 worker 线程:主线程创建 worker 线程,worker 线程的 Runnable 任务是一个无限循环,检查中断标志位并模拟工作
2.启动 worker 线程:调用 worker.start(),worker 线程开始执行。
(2) worker 线程执行
1.进入循环:worker 线程进入 while (!Thread.currentThread().isInterrupted()) 循环。
2.输出日志:打印 “Working…”。
3.模拟工作:调用 Thread.sleep(500),线程休眠 500 毫秒。
4.检查中断:如果 worker 线程在休眠期间被中断,会抛出 InterruptedException。
5.捕获异常:捕获 InterruptedException,打印 “Thread interrupted while sleeping”。
6.重新设置中断标志位:调用 Thread.currentThread().interrupt(),重新设置中断标志位。
7.退出循环:由于中断标志位被重新设置,while 循环条件为 false,退出循环。
8.输出日志:打印 “Thread stopped gracefully”。
9.线程终止:worker 线程执行完毕,进入终止状态。
(3) 主线程执行
1.主线程休眠:主线程调用 Thread.sleep(2000),休眠 2 秒。
2.中断 worker 线程:主线程休眠结束后,调用 worker.interrupt(),请求中断 worker 线程。
3.主线程终止:主线程执行完毕,程序结束
7.yield()
方法定义就是一个静态本地方法:暂停当前正在执行的线程对象,并执行其他线程。意义不太大
public static native void yield();
拿出来主要是和sleep方法做区分说明
public static native void sleep(long millis) throws InterruptedException;
1.都是native方法 说明是操作系统控制或者JVM控制方法
- sleep()方法操作系统控制,给其他线程运行机会时不考虑线程的优先级,因此会给低优先级的线程以运行的机会;
- yield()方法是jvm控制:用于提醒调度器当前线程愿意放弃CPU资源,让其他具有相同或更高优先级的线程有机会执行
2.都使用了static说明都是静态方法,为什么都定义成静态方法
- 首先Thread 类的 sleep()和 yield()方法,两者的作用是让出资源,将在当前正在执行的线程上从运行状态变成就绪状态,其他正在处于等待的线程调用这些方法是没有意义的
- 其次如果sleep和yield是实例方法,那就热闹了。就可以让一个线程可以获取其他线程对象的引用,然后通过引用调要其他线程的sleep和yield方法,让其他线程让出CPU使用权。如果每个线程都可以通过sleep、yield其他线程的方式来让自己获得CPU使用权,那不是世界大乱了。线程之间可以互相sleep,互相yield,要确保每个线程在运行时是独立的