Thread 类详解
本文共6485字,阅读本文大概需要13~22分钟
初始化过程
public Thread() {
// 4个参数
// 第1个,ThreadGroup 线程组
// 第2个,Runnable
// 第3个,name,默认情况下,如果你不指定线程的名称,
// 那么自动给你生成的线程名称就是,Thread-0,Thread-1......
// 第4个,stackSize
init(null, null, "Thread-" + nextThreadNum(), 0);
}
进入 init 方法,会发现如下代码块
// 当你创建线程的时候,获取到的是 currentThread(),是当前线程创建你的那个线程
// 比如说,你创建一个线程的时候,默认它的父线程就是创建它的那个线程,
// 比如说 main 线程创建一个 ServiceAliveMonitor 线程,此时 ServiceAliveMonitor 线程的父线程就是 main 线程
Thread parent = currentThread();
// 下面这段代码的意思是,ThreadGroup 是不指定的,那么就会自动给你处理一下,给你分配一个线程组,
// 每个线程组必须属于一个 ThreadGroup的,如果你没有指定线程组,那么默认的线程组就是父线程的线程组
// 如果说,你的父线程是 main 线程的话,那么你的线程组就是 main 线程的线程组(main 线程组)
if (g == null) {
/* Determine if it's an applet or not */
/* If there is a security manager, ask the security manager
what to do. */
if (security != null) {
g = security.getThreadGroup();
}
/* If the security doesn't have a strong opinion of the matter
use the parent thread group. */
if (g == null) {
g = parent.getThreadGroup();
}
}
......
// 默认情况下,如果你没有指定你是否为 daemon 的话,那么你的 daemon 的状态是有父线程决定的
// 就是说,如果你的父线程是 daemon 线程,那么你也是 daemon 线程
// daemon 守护线程
this.daemon = parent.isDaemon();
// 同理,你的优先级如果没有指定的话,那么就跟父线程的优先级保持一致
// 若不设置优先级,默认为5
this.priority = parent.getPriority();
其实每个线程都有一个线程 id,threadId,第一个分配的线程,它的 id 是 1,之后的线程是2,3,4...以此类推,先关代码块如下
public Thread() {
init(null, null, "Thread-" + nextThreadNum(), 0);
}
private static int threadInitNumber;
private static synchronized int nextThreadNum() {
return threadInitNumber++;
}
初始化总结
- 创建你的线程,就是你的父线程
- 如果你没有指定 ThreadGroup,你的 ThreadGroup 就是父线程的 ThreadGroup
- 你的 daemon 状态默认是父线程的 daemon 状态
- 你的优先级默认是父线程的优先级
- 如果你没有指定线程的名称,那么默认就是 Thread-0 的格式名称
- 你的线程 id 全局递增,从 1 开始的
start()
永远都不能对一个线程多次调用和执行 start() 方法
// It is never legal to start a thread more than once.
// In particular, a thread may not be restarted once it has completed execution.
// 这句话的意思是,永远都不能对一个线程多次调用和执行 start() 方法,这个是不对的
public synchronized void start() {......}
进入 start() 方法,你会看到如下代码块
// 如果你的线程一旦执行过一次以后,那么它的 threadStatus就会变成非 0 的一个状态
// 如果 threadStatus 是非 0 的状态,说明它之前已经被执行过了
// 这里会有一个判断,如果你对一个线程多次执行 start() 方法,会抛出非法非线程状态异常IllegalThreadStateException
if (threadStatus != 0)
throw new IllegalThreadStateException();
// group 就是之前给分配的,如果你自己指定了那么就是你自己创建的那个 ThreadGroup
// public Thread(ThreadGroup group, String name){}
// 上面是 Thread 的一个构造方法,可以传 ThreadGroup
// 否则就是你的父线程的 threadGroup,
// 这行代码就是将当前线程加入了它属于的那个线程组
group.add(this);
// 下面这段主要是调用 start0() 这个方法
boolean started = false;
try {
start0();
started = true;
} finally {
try {
if (!started) {
group.threadStartFailed(this);
}
} catch (Throwable ignore) {
/* do nothing. If start0 threw a Throwable then
it will be passed up the call stack */
}
}
// 会结合底层的一些代码和机制,实际的启动一个线程
// 一旦 start0() 成功启动之后,就会去执行我们覆盖掉的那个 run() 方法
// @Override
// public void run() {
// if (target != null) {
// target.run();
// }
// }
// 或者是如果你传入进去的是那个 Runnable 对象,人家就会执行那个 Runnable 对象的方法
private native void start0();
需要注意的点
- 一旦启动了线程之后,就不能再重新启动了,不能多次调用 start() 方法,因为启动之后, threadStatus 就会变成非 0 状态了,此时就不能重新调用了
- 你启动线程之后,这个线程机会加入之前处理好的那个线程组中
- 启动一个线程实际上走的是 native 方法,start0()会实际的启动一个线程
- 一个线程启动之后就会执行 run() 方法
sleep()
这个方法主要是指定要等待多少毫秒
在 jdk1.5 之后引入了 TimeUtil 这个类,可以使用TimeUnit.HOURS.sleep(n)
指定睡眠几个小时,使用TimeUnit.SECONDS.sleep(n)
指定睡眠几秒钟
但是在平时使用或者开源项目中,在线程 sleep 这块,还是用的最最原始的 sleep,因为可以通过毫秒树,动态的传入一个外卖配置的一个值,比如300毫秒就传300,30秒就传 30 * 1000,1分钟就传 1 * 60 * 1000
如果用 TimeUtil 的话,在外面怎么配?你要配置休眠的 5 分钟的话,还得加上 1 个单位,代码里要判断一下你休眠的时间单位,如果是分钟,那么还得用 TimeUtil.MINIUTE 来进行休眠,不太方便
yield()
// 官方对该方法有如下注释
/**
* A hint to the scheduler that the current thread is willing to yield
* its current use of a processor. The scheduler is free to ignore this
* hint.
*
* <p> Yield is a heuristic attempt to improve relative progression
* between threads that would otherwise over-utilise a CPU. Its use
* should be combined with detailed profiling and benchmarking to
* ensure that it actually has the desired effect.
*
* <p> It is rarely appropriate to use this method. It may be useful
* for debugging or testing purposes, where it may help to reproduce
* bugs due to race conditions. It may also be useful when designing
* concurrency control constructs such as the ones in the
* {@link java.util.concurrent.locks} package.
*/
// 意思是说,该方法只是对线程调度器的一个建议,而且也只是建议具有相同优先级的其他线程可以运行
//
// yield,使当前线程正在执行的时候停止下来进入等待队列
// 回到等待队列里在系统的调度算法里还是依然有可能把你这个线程拿回去来继续执行
// 当然更大的可能性是把原来等待的那些线程拿出来一个来执行
// 因此,yield的意思是我让出一下 CPU,后面你能不能抢到那我不管
public static native void yield();
join()
main 线程里面若开启了一个其他线程,这个时候只要你一旦开启了其他线程之后,那么 main 线程就会跟其他线程开始并发的运行,一会儿执行 mian 线程,一会儿执行其他线程的代码
若有两个线程 t1 线程和 t2 线程,在 t1 线程调用了 join 方法,那么就会导致 t1 线程阻塞住,他会等待其他线程(t2线程)的代码逻辑执行结束,然后 t1 线程才会继续执行
public final synchronized void join(long millis)
- 需求:
- 若有三个线程 t1,t2,t3
- 要保证 t1 线程先执行完毕,再执行 t2 线程,最后执行 t3 线程
// 在 t2 线程调用 t1.join()
// 在 t3 线程调用 t2.join()
public class Thread_Join {
public static void main(String[] args) {
testJoin();
}
private static void testJoin() {
Thread t1 = new Thread(()->{
for (int i = 0; i < 10; i++) {
System.out.println("t1-" + i);
}
});
Thread t2 = new Thread(()->{
try {
t1.join();
} catch (InterruptedException e) {
e.printStackTrace();
}
for (int i = 0; i < 10; i++) {
System.out.println("t2-" + i);
}
});
Thread t3 = new Thread(()->{
try {
t2.join();
} catch (InterruptedException e) {
e.printStackTrace();
}
for (int i = 0; i < 10; i++) {
System.out.println("t3-" + i);
}
});
t1.start();t2.start();t3.start();
}
}
interrupt()
通过interrupt()方法可以中断线程。如果你是 while循环,可以判断如果没有被中断,那么就正常工作,如果别人中断了这个线程,那么 while 循环的条件判断里,就会发现说,被中断了。被中断以后,你的 while 循环被发现了,就会退出循环,这个线程就终止了
public class InterruptDemo {
public static void main(String[] args) throws Exception{
MyThread thread = new MyThread();
thread.start();
Thread.sleep(1000);
thread.setShouldRun(false);
thread.interrupt();
}
private static class MyThread extends Thread {
private Boolean shouldRun = true;
@Override
public void run() {
while(shouldRun) {
try {
System.out.println("线程1在执行工作......");
Thread.sleep(30 * 1000);
} catch (Exception e) {
e.printStackTrace();
}
}
}
public void setShouldRun(Boolean shouldRun) {
this.shouldRun = shouldRun;
}
}
}
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 阿里最新开源QwQ-32B,效果媲美deepseek-r1满血版,部署成本又又又降低了!
· SQL Server 2025 AI相关能力初探
· AI编程工具终极对决:字节Trae VS Cursor,谁才是开发者新宠?
· 开源Multi-agent AI智能体框架aevatar.ai,欢迎大家贡献代码
· Manus重磅发布:全球首款通用AI代理技术深度解析与实战指南