外观
聊一下什么是线程
约 2108 字大约 7 分钟
2025-10-19
什么是Java线程
Java线程,本质是对操作系统线程的 “包装”。底层,还是要依赖操作系统的线程机制(比如 Linux 的 pthread,Windows 的 Win32 线程)。
可以这么理解,Java线程是 “中间商”,程序员和 “中间商” 打交道,但实际干活的,还是操作系统的线程 。
进程和线程是什么关系
进程是系统资源分配的最小单位,一个程序一个进程,而线程是进程中的一个执行序列。比如,我们打开一个app,手机操作系统会开启一个进程,但是背后我们对app的操作,可能是多个线程在执行。
创建Java线程的过程
开启线程
常用的是通过new Thread(Runnale target).start()的方式创建并运行一个新线程。start()方法背后做了那些事?
public synchronized void start() {
// 1. 检查线程状态,只有 NEW 状态才能启动
if (threadStatus != 0)
throw new IllegalThreadStateException();
// 2. 把线程加入到线程组(ThreadGroup)
group.add(this);
boolean started = false;
try {
// 3. 调用 native 方法 start0(),这是关键!
start0();
started = true;
} finally {
try {
if (!started) {
// 4. 如果启动失败,从线程组里移除
group.threadStartFailed(this);
}
} catch (Throwable ignore) {
// 忽略异常,避免影响后续逻辑
}
}
}
// native 方法,真正创建操作系统线程的地方
private native void start0();从上面这段源码,调用start()方法,先检查线程状态,只有状态为 “NEW” 的线程能启动,然后调用native方法 start0(),让操作系统创建一个新的线程,这里的native方法是指jdk中使用c++实现的方法,暂时把它当作一个黑盒。
新线程创建成功后,会去执行实现Runnable接口实现类的run()方法里的逻辑。一般我们写的程序逻辑也都在run方法里面。
线程状态机
Java线程有6种状态,定义在Thread.State枚举里。
public enum State {
NEW, // 新建状态:刚new出来,还没start()
RUNNABLE, // 可运行状态:start()之后,正在运行或等待CPU
BLOCKED, // 阻塞状态:等待锁(比如synchronized没拿到锁)
WAITING, // 等待状态:无期限等待(比如wait()、join())
TIMED_WAITING,// 超时等待状态:有期限等待(比如sleep(1000))
TERMINATED; // 终止状态:run()执行完或异常退出
}- 状态扭转过程
NEW → RUNNABLE
调用
start()时,只有threadStatus == 0才能调用start()。调用start0()后,操作系统创建线程,线程状态就变成RUNNABLE。这里要注意:
RUNNABLE状态包含两种情况,要么线程正在 CPU 上执行(Running),要么在等待CPU分配时间片(Ready)。Java 里没有把这两种状态分开,统一叫 RUNNABLE。RUNNABLE → BLOCKED
当线程执行 synchronized 代码块,或者调用
synchronized方法时,如果锁已经被其他线程占用,当前线程就会进入 BLOCKED 状态。这背后的逻辑跟 JVM 的 “对象监视器”(Monitor)有关。每个对象都有个 Monitor,线程要执行同步代码,必须先拿到 Monitor 的锁。如果拿不到,线程就会被放到 Monitor 的 “阻塞队列” 里,状态变成 BLOCKED。
两个线程抢同一个锁,线程 A 先拿到锁,线程 B 没拿到,就会进入 BLOCKED 状态,直到线程 A 释放锁,线程 B 才有机会拿到锁,回到 RUNNABLE 状态。
RUNNABLE → WAITING
当线程调用
Object.wait()(无参数版)、Thread.join()(无参数版)等方法时,会进入 WAITING 状态,而且是 “无期限等待”—— 除非被其他线程唤醒(比如调用 notify()),否则会一直等下去。 咱们看 wait() 方法的源码(在 Object 类里,因为每个对象都有 wait ()):
public final void wait() throws InterruptedException {
wait(0); // 调用带参数的wait(),参数0表示无期限
}
public final native void wait(long timeout) throws InterruptedException;这里要注意,调用 wait() 必须先拿到对象的锁,调用后会释放锁,线程进入 WAITING 状态,被放到 Monitor 的 “等待队列” 里。其他线程拿到锁后调用 notify(),会把等待队列里的一个线程唤醒,唤醒后的线程会回到 BLOCKED 状态,重新抢锁。
比如,线程 A 调用 obj.wait(),释放 obj 的锁,进入 WAITING 状态;线程 B 拿到 obj 的锁,执行完后调用 obj.notify(),线程 A 被唤醒,进入 BLOCKED 状态,等线程 B 释放锁后,线程 A 抢锁,抢到后回到 RUNNABLE 状态。
RUNNABLE → TIMED_WAITING
跟 WAITING 类似,但 TIMED_WAITING 是 “有期限等待”,比如调用
Thread.sleep(1000)、Object.wait(1000)、Thread.join(1000)等方法。 以 sleep() 方法为例,源码是这样的:
public static native void sleep(long millis) throws InterruptedException;这是个native方法,调用后线程会进入 TIMED_WAITING 状态,等待指定的时间。时间到了之后,线程会自动回到 RUNNABLE 状态。而且 sleep() 不会释放锁,这一点跟 wait() 不一样。
比如,线程 A 拿到锁后调用 sleep(1000),虽然进入了 TIMED_WAITING 状态,但锁还在手里,其他线程想拿锁只能等着。1 秒后线程 A 醒来,继续执行同步代码,释放锁后其他线程才能抢。
所有状态 → TERMINATED
当线程的 run() 方法执行完,或者在执行过程中抛出未捕获的异常,线程就会进入TERMINATED状态。一旦进入这个状态,线程就 “死透了”,再也不能回到其他状态, 哪怕你再调用
start(),也会抛出IllegalThreadStateException。咱们可以用
Thread.isAlive()方法判断线程是否还 “活着”(也就是状态不是NEW和TERMINATED),源码里 isAlive() 是这么判断的:
public final native boolean isAlive();底层其实就是检查线程的状态,只要不是 NEW 和 TERMINATED,就返回 true。
如何调度
线程创建出来后,哪个线程先执行、哪个线程后执行,是 “线程调度” 的活儿。Java的线程调度主要依赖两个机制,优先级调度和时间片轮转。
线程优先级
Java线程有10个优先级,从1(最低)到 10(最高,定义在Thread类里
public final static int MIN_PRIORITY = 1;
public final static int NORM_PRIORITY = 5; // 默认优先级
public final static int MAX_PRIORITY = 10;咱们可以通过 setPriority(int newPriority) 方法设置线程优先级,源码里会先检查优先级是否合法(必须在 1-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);
}
}
private native void setPriority0(int newPriority);Java的线程优先级只是“建议”,最终还是要看操作系统。
比如 Windows只支持7个优先级,Java的10个优先级会映射到Windows的7个优先级上,而且有些操作系统是 “抢占式调度”,优先级高的线程会抢占优先级低的线程的CPU资源,但有些系统可能不支持。
所以别太依赖线程优先级,比如你把线程A的优先级设为10,线程B设1,也不能保证线程A一定先执行。只能说线程A有更大的概率被调度。
CPU时间片轮换
除了优先级,操作系统还会给每个线程分配 “时间片”(比如 10ms)。 线程在时间片内执行,时间到了之后,操作系统会把CPU资源分配给其他线程,当前线程回到就绪状态(属于 RUNNABLE),等待下一个时间片。
这种方式能保证每个线程都有机会执行,不会出现某个线程一直占用CP 的情况。Java本身不负责时间片的分配,而是由操作系统来管理,Java线程只是 “被动接受” 调度。
举个例子,你同时开了三个线程,线程 A、B、C。操作系统给每个线程分配10ms的时间片,先让A执行10ms,时间到了A暂停,让B执行10ms,B暂停后让C执行10ms,然后再回到A,循环往复,直到线程执行完。因为时间片很短,看起来就像三个线程在 “同时执行”。 这也是为什么单核CUP可以执行多线程的原理。