创建线程的方式
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(() -> { System.out.println("线程状态:" + Thread.currentThread().getState());
synchronized (lock) { try { Thread.sleep(1000);
lock.wait(); } catch (InterruptedException e) { e.printStackTrace(); } } });
System.out.println("线程状态:" + t.getState()); t.start();
Thread.sleep(100); System.out.println("线程状态:" + t.getState());
Thread.sleep(1100); System.out.println("线程状态:" + t.getState());
synchronized (lock) { lock.notify(); } t.join();
System.out.println("线程状态:" + t.getState()); } }
|
使用jstack查看线程状态
1.运行一个会卡住的线程程序
2.获取进程ID
3.使用jstack查看线程状态
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. 所属类不同
sleep:Thread 类的静态方法
wait:Object 类的方法
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. 使用场景
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 线程
- 后台心跳、监控、日志上报
- 后台周期性清理任务