java线程基础

创建线程的方式

1. 继承 Thread 类

  • 自定义类继承 Thread,重写 run() 方法
  • 创建实例,调用 start() 启动线程
  • 缺点:Java 单继承,无法再继承其他类
1
2
3
4
5
6
7
8
9
10
11
12
13
class MyThread extends Thread {
@Override
public void run() {
System.out.println("线程1:继承 Thread 运行");
}
}

public class Test {
public static void main(String[] args) {
MyThread t = new MyThread();
t.start(); // 启动线程
}
}

2. 实现 Runnable 接口

  • 实现 Runnable 接口,重写 run()
  • 传入 Thread 构造,调用 start()
  • 优点:避免单继承限制,便于共享任务
1
2
3
4
5
6
7
8
9
10
11
12
13
class MyRunnable implements Runnable {
@Override
public void run() {
System.out.println("线程2:实现 Runnable 运行");
}
}

public class Test {
public static void main(String[] args) {
Thread t = new Thread(new MyRunnable());
t.start();
}
}

3. 实现 Callable 接口 + FutureTask

  • 实现 Callable,重写 call()
  • 包装成 FutureTask,再交给 Thread
  • 优点:可以有返回值、可以抛出异常
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
import java.util.concurrent.Callable;
import java.util.concurrent.FutureTask;

class MyCallable implements Callable<Integer> {
@Override
public Integer call() throws Exception {
System.out.println("线程3:Callable 运行");
return 100;
}
}

public class Test {
public static void main(String[] args) throws Exception {
FutureTask<Integer> task = new FutureTask<>(new MyCallable());
new Thread(task).start();

// 获取返回值
Integer result = task.get();
System.out.println("返回结果:" + result);
}
}

4. 使用线程池(ExecutorService)

  • 通过 ThreadPoolExecutor 或工具类创建线程池
  • 调用 execute() / submit() 执行任务
  • 优点:复用线程、控制并发数、避免频繁创建销毁
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

public class Test {
public static void main(String[] args) {
ExecutorService pool = Executors.newFixedThreadPool(3);

pool.execute(() -> {
System.out.println("线程4:线程池执行 Runnable");
});

pool.submit(() -> {
System.out.println("线程4:线程池执行 Callable");
return 200;
});

pool.shutdown();
}
}

线程 6 种生命周期

1. 新建(NEW)

  • 创建了 Thread 对象,但还没调用 start()
  • 仅在内存中初始化,未进入调度

2. 可运行(RUNNABLE)

  • 调用 start() 后进入该状态
  • 包含正在运行 + 等待 CPU 时间片两种情况
  • Java 不细分 Running 和 Ready,统一叫 RUNNABLE

3. 阻塞(BLOCKED)

  • 等待获取 synchronized
  • 锁被其他线程持有,当前线程排队等锁

4. 等待(WAITING)

  • 无限期等待,需其他线程显式唤醒
  • 进入场景:
    • Object.wait()
    • Thread.join()
    • LockSupport.park()

5. 定时等待(TIMED_WAITING)

  • 有限时间等待,时间到自动唤醒
  • 进入场景:
    • Thread.sleep(long)
    • Object.wait(long)
    • Thread.join(long)
    • LockSupport.parkNanos() / parkUntil()

6. 终止(TERMINATED)

  • run() 执行完毕
  • 线程异常退出
  • 线程生命周期结束
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
NEW
↓(调用 start())
RUNNABLE ←←←←←←←←←←←←←←←←←←←←←←←←←←←┓
↓↑(获得CPU/时间片用完) ┃
↓ ┃
├─────────────────────────────────┐ ┃
│ 进入 synchronized 锁竞争失败 │ → BLOCKED
│ │ ↑
└─────────────────────────────────┘ │

WAITING │
↑(wait() / join() / park()) │
↓(notify() / interrupt() / unpark())→─┘

TIMED_WAITING
↑(sleep(long) / wait(long) / parkNanos())
↓(时间到 / 唤醒 / 中断)→ RUNNABLE

RUNNABLE
↓(run() 执行完毕 / 异常退出)
TERMINATED

RUNNABLE:才占用 CPU、参与时间片调度

BLOCKED / WAITING / TIMED_WAITING:都不占 CPU

示例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
public class ThreadStateDemo {
public static void main(String[] args) throws InterruptedException {
// 锁对象
final Object lock = new Object();

Thread t = new Thread(() -> {
// 2. RUNNABLE
System.out.println("线程状态:" + Thread.currentThread().getState());

synchronized (lock) {
try {
// 3. TIMED_WAITING
Thread.sleep(1000);

// 4. WAITING
lock.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
});

// 1. NEW
System.out.println("线程状态:" + t.getState());
t.start();

// 等线程进入 sleep
Thread.sleep(100);
System.out.println("线程状态:" + t.getState());

// 等线程进入 wait()
Thread.sleep(1100);
System.out.println("线程状态:" + t.getState());

// 唤醒线程,让它结束
synchronized (lock) {
lock.notify();
}
t.join();

// 6. TERMINATED
System.out.println("线程状态:" + t.getState());
}
}

使用jstack查看线程状态

1.运行一个会卡住的线程程序

2.获取进程ID

1
jps

3.使用jstack查看线程状态

1
jstack <PID>
1
2
3
java.lang.Thread.State: BLOCKED (on object monitor)
java.lang.Thread.State: WAITING (on object monitor)
java.lang.Thread.State: TIMED_WAITING (sleeping)

终止线程或结束线程

1. 正常终止

  • run() 方法执行完毕,线程自动结束
  • call() 执行完毕(Callable)
1
2
3
new Thread(() -> {
// 代码执行完,线程自然终止
}).start();

2. 异常终止

  • 线程内抛出未捕获异常
  • JVM 会标记线程为 TERMINATED
1
2
3
new Thread(() -> {
throw new RuntimeException("异常退出");
}).start();

3. 优雅停止线程(推荐)

使用标志位控制循环退出,安全干净。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
class MyThread extends Thread {
private volatile boolean stop = false;

public void stopThread() {
stop = true;
}

@Override
public void run() {
while (!stop) {
// 执行业务
}
}
}

4. 使用 interrupt () 中断(标准方式)

  • interrupt() 只是设置中断标志,不强制杀死线程
  • 配合 isInterrupted() 或异常处理退出
1
2
3
4
5
6
7
8
Thread t = new Thread(() -> {
while (!Thread.currentThread().isInterrupted()) {
// 任务
}
});
t.start();

t.interrupt(); // 中断

如果线程在 sleep / wait / join,会抛出 InterruptedException,捕获后退出即可。


5. 不推荐、已废弃的方法

  • **stop()**:暴力杀死线程,锁不释放,数据不一致,极度危险
  • **suspend() / resume()**:易死锁,已废弃

常见方法

1. yield () 方法

  • 静态方法:Thread.yield()
  • 作用:当前线程让出 CPU 时间片
  • 效果:从 Running 回到 RUNNABLE 状态,参与下一次 CPU 竞争
  • 特点:
    • 不会释放锁
    • 不会阻塞,不会进入 WAITING / BLOCKED
    • 调度依赖操作系统,不保证立刻切换
  • 使用场景:让优先级高的线程优先执行,避免当前线程独占 CPU

2. join () 方法

  • 实例方法:t.join() / t.join(long timeout)
  • 作用:当前线程等待目标线程 t 执行完毕再继续
  • 效果:当前线程进入 WAITING / TIMED_WAITING
  • 特点:
    • 谁调用 join,谁等待
    • 底层是 wait() 实现,会释放锁
  • 使用场景:
    • 线程间串行执行
    • 等待子线程完成再获取结果

sleep 与 wait 的区别

1. 所属类不同

  • sleepThread 类的静态方法
  • waitObject 类的方法

2. 是否释放锁

  • sleep不释放锁,抱着锁睡觉
  • wait释放锁,进入等待队列

3. 使用位置

  • sleep:任何地方都能用
  • wait:必须在 synchronized 同步块内 使用

4. 唤醒方式

  • sleep:时间到自动唤醒
  • wait:必须通过 notify() / notifyAll() 唤醒,或被中断

5. 线程状态

  • sleep:进入 TIMED_WAITING
  • wait():进入 WAITING
  • wait(long):进入 TIMED_WAITING

start () 与 run () 区别

1. 本质作用

  • start()启动一个新线程,由操作系统调度执行 run() 方法
  • run():仅仅是Thread 里的一个普通方法,不会创建新线程

2. 是否开启线程

  • start():会真正创建新线程,异步执行
  • run():在 当前线程(比如 main 线程)直接同步调用

3. 调用次数限制

  • start()只能调用一次,多次抛 IllegalThreadStateException
  • run():普通方法,可以多次调用

4. 线程状态

  • start():线程从 NEW → RUNNABLE
  • run():不会改变线程状态

5. 底层机制

  • start():会调用**本地方法 start0 ()**,与操作系统交互创建线程
  • run():只是简单的方法执行,无任何底层操作

notify 和 notifyAll 区别

1. 唤醒范围

  • notify()随机唤醒等待队列中的 1 个线程
  • notifyAll()唤醒等待队列中所有等待的线程

2. 锁竞争情况

  • notify():只有 1 个线程去竞争锁,压力小
  • notifyAll():所有被唤醒的线程一起抢锁,竞争激烈

3. 安全性

  • notify():可能导致部分线程永久等待(信号丢失、唤醒丢失)
  • notifyAll():更安全,不会出现唤醒丢失

4. 使用场景

  • notify()
    
    1
    2
    3
    4
    5
    6

    - 多生产者单消费者
    - 所有等待线程都是**同种任务**,唤醒谁都一样

    - ```
    notifyAll()
    - 线程等待原因不同 - 生产消费模型(确保消费者一定能被唤醒)

5. 底层前提

  • 都必须在 synchronized 内部使用
  • 唤醒后线程不会立刻执行,要先重新获取锁

守护线程与用户线程

1. 基本定义

  • 用户线程(User Thread)
    • JVM 正常工作的主线线程
    • 我们默认创建的线程都是用户线程
  • 守护线程(Daemon Thread)
    • 为用户线程提供后台服务的线程
    • 如:GC 垃圾回收线程

2. 核心区别

  • JVM 退出规则:
    • 所有用户线程都执行完毕 → JVM 才会退出
    • 只要还有用户线程在跑,JVM 就不会退出
    • 守护线程不影响 JVM 退出
    • 当最后一个用户线程结束,JVM 直接退出,不管守护线程有没有执行完

3. 设置方式

  • start()之前调用:

    1
    2
    thread.setDaemon(true);
    thread.start();
  • 默认是 false,即用户线程

4. 特性

  • 守护线程中创建的线程,默认也是守护线程
  • 守护线程不应该用于操作业务逻辑、读写文件等
    • 因为随时可能随 JVM 退出而终止,数据可能不完整

5. 典型场景

  • GC 线程
  • 后台心跳、监控、日志上报
  • 后台周期性清理任务

java线程基础
http://hanqichuan.com/2026/04/15/java并发/java线程基础/
作者
韩启川
发布于
2026年4月15日
许可协议