1. 线程的基本概念
线程是什么?
- 线程是程序执行的最小单位,是操作系统能够进行调度的基本单元。
- 一个程序至少包含一个线程(主线程),它由
main
方法启动。 - 线程共享同一个进程的资源,例如内存空间,但线程本身有独立的运行栈和程序计数器。
线程的生命周期
Java线程的生命周期可以分为以下几个状态:
-
New(新建)
- 当线程对象被创建,但还没有调用
start
方法时,线程处于新建状态。
示例:
Thread thread = new Thread();
- 当线程对象被创建,但还没有调用
-
Runnable(可运行)
- 调用了
start
方法后,线程进入可运行状态。注意,它不一定立即运行,而是等待CPU调度。
示例:
thread.start();
- 调用了
-
Running(运行中)
- 当CPU开始执行该线程的代码时,线程进入运行状态。
-
Blocked(阻塞)
- 当线程尝试获取一个被其他线程占用的锁,或者等待资源释放时,线程进入阻塞状态。
-
Waiting/Timed Waiting(等待/超时等待)
- Waiting:线程主动调用
wait
方法,等待其他线程唤醒。 - Timed Waiting:线程调用
sleep
或带超时参数的wait
方法,指定等待时间后自动唤醒。
- Waiting:线程主动调用
-
Terminated(终止)
- 当线程的
run
方法执行完毕或被显式中止时,线程进入终止状态。
示例:
System.out.println("Thread finished.");
- 当线程的
2. 线程的创建与启动
创建线程的两种基础方式
-
继承
Thread
类
创建一个子类继承Thread
类,并重写其run
方法。class MyThread extends Thread { @Override public void run() { System.out.println("Thread is running..."); } } public class Main { public static void main(String[] args) { MyThread thread = new MyThread(); thread.start(); // 启动线程 } }
-
实现
Runnable
接口
创建一个实现Runnable
接口的类,并将其传递给Thread
对象。class MyRunnable implements Runnable { @Override public void run() { System.out.println("Thread is running..."); } } public class Main { public static void main(String[] args) { Thread thread = new Thread(new MyRunnable()); thread.start(); // 启动线程 } }
start
方法和run
方法的区别
-
start
方法- 用于启动线程,将线程加入到就绪队列,等待CPU调度。
- 它会调用线程的
run
方法,但在一个独立的线程中执行。 - 每个线程只能调用一次
start
方法,重复调用会抛出IllegalThreadStateException
。 - 示例:
Thread thread = new Thread(() -> System.out.println("Running in a new thread!")); thread.start(); // 启动线程
-
run
方法- 是
Thread
类中的普通方法,定义了线程的任务逻辑。 - 如果直接调用
run
方法,它会在当前线程中执行,而不是创建一个新线程。 - 示例:
Thread thread = new Thread(() -> System.out.println("Running in current thread!")); thread.run(); // 没有启动新线程
- 是
区别总结
特性 | start 方法 | run 方法 |
---|---|---|
是否创建新线程 | 是 | 否 |
线程状态 | 切换到 Runnable 状态,等待调度 | 直接在调用线程中执行 |
调用次数 | 只能调用一次,重复调用抛异常 | 可多次调用 |
3. 线程的控制方法
1. wait
方法
-
作用:使当前线程进入等待状态,并释放锁,直到其他线程调用
notify
或notifyAll
唤醒它。 -
关键点:
- 必须在同步块或同步方法中使用,否则会抛出
IllegalMonitorStateException
。 - 调用
wait
后,线程进入WAITING
状态,等待被唤醒。
- 必须在同步块或同步方法中使用,否则会抛出
-
示例代码:
class WaitExample { private static final Object lock = new Object(); public static void main(String[] args) { Thread thread1 = new Thread(() -> { synchronized (lock) { try { System.out.println("Thread1: Waiting..."); lock.wait(); System.out.println("Thread1: Resumed!"); } catch (InterruptedException e) { e.printStackTrace(); } } }); Thread thread2 = new Thread(() -> { synchronized (lock) { System.out.println("Thread2: Notifying..."); lock.notify(); } }); thread1.start(); try { Thread.sleep(100); } catch (InterruptedException e) { e.printStackTrace(); } thread2.start(); } }
-
输出:
Thread1: Waiting... Thread2: Notifying... Thread1: Resumed!
2. sleep
方法
-
作用:让当前线程暂停指定时间,不释放锁,进入
TIMED_WAITING
状态。 -
关键点:
- 它是静态方法,作用于当前线程。
- 不影响锁的持有状态,其他线程仍然无法访问被锁住的资源。
-
示例代码:
public class SleepExample { public static void main(String[] args) { Thread thread = new Thread(() -> { System.out.println("Thread: Sleeping..."); try { Thread.sleep(2000); // 暂停2秒 } catch (InterruptedException e) { e.printStackTrace(); } System.out.println("Thread: Awake!"); }); thread.start(); } }
-
输出:
Thread: Sleeping... (等待2秒) Thread: Awake!
3. join
方法
-
作用:让一个线程等待另一个线程的完成。
-
关键点:
join
可以指定等待时间,超时后会继续执行。- 调用线程进入
WAITING
或TIMED_WAITING
状态,直到目标线程完成或时间耗尽。
-
示例代码:
public class JoinExample { public static void main(String[] args) { Thread thread = new Thread(() -> { System.out.println("Thread: Working..."); try { Thread.sleep(3000); // 模拟耗时任务 } catch (InterruptedException e) { e.printStackTrace(); } System.out.println("Thread: Done!"); }); thread.start(); System.out.println("Main thread: Waiting..."); try { thread.join(); // 等待thread完成 } catch (InterruptedException e) { e.printStackTrace(); } System.out.println("Main thread: Resumed!"); } }
-
输出:
Thread: Working... Main thread: Waiting... (等待3秒) Thread: Done! Main thread: Resumed!
4. yield
方法
-
作用:提示线程调度器当前线程愿意让出CPU时间,但不一定会立即切换到其他线程。
-
关键点:
- 调用
yield
后,线程可能再次被调度。 - 用于降低线程优先级,提高CPU资源利用率。
- 调用
-
示例代码:
public class YieldExample { public static void main(String[] args) { Runnable task = () -> { for (int i = 0; i < 5; i++) { System.out.println(Thread.currentThread().getName() + ": " + i); Thread.yield(); // 提示让出CPU } }; Thread thread1 = new Thread(task, "Thread1"); Thread thread2 = new Thread(task, "Thread2"); thread1.start(); thread2.start(); } }
-
输出:
Thread1: 0 Thread2: 0 Thread1: 1 Thread2: 1 ...
5. Blocked状态
- 当线程试图进入一个已被其他线程锁住的同步代码块或方法时,线程进入**Blocked(阻塞)**状态。
- 示例:
class BlockedExample { private static final Object lock = new Object(); public static void main(String[] args) { Thread thread1 = new Thread(() -> { synchronized (lock) { System.out.println("Thread1: Holding lock..."); try { Thread.sleep(5000); // 模拟任务 } catch (InterruptedException e) { e.printStackTrace(); } System.out.println("Thread1: Released lock."); } }); Thread thread2 = new Thread(() -> { synchronized (lock) { System.out.println("Thread2: Acquired lock."); } }); thread1.start(); thread2.start(); } }
- 输出:
Thread1: Holding lock... (Thread2进入Blocked状态,等待lock释放) Thread1: Released lock. Thread2: Acquired lock.
4. 使用jstack
监控线程状态
1. jstack
简介
jstack
是 JDK 提供的一个命令行工具,用于生成 Java 虚拟机当前线程的堆栈快照(Thread Dump)。- 主要功能:
- 查看每个线程的状态(如
RUNNABLE
、BLOCKED
、WAITING
等)。 - 调试死锁问题。
- 诊断线程长时间运行或无响应的问题。
- 查看每个线程的状态(如
2. 常见线程状态
NEW
:线程刚创建,尚未调用start
。RUNNABLE
:线程运行中,或正在等待 CPU 时间片。BLOCKED
:线程在等待进入同步代码块,锁未释放。WAITING
:线程调用wait()
或join()
,等待被唤醒。TIMED_WAITING
:线程调用sleep()
或带时间参数的wait()
或join()
。TERMINATED
:线程运行结束。
3. 使用jstack
的基本命令
- 获取目标 JVM 进程的线程堆栈:
jstack <pid>
<pid>
是目标进程的 ID。可以通过jps
命令获取。
- 将线程堆栈保存到文件:
jstack <pid> > thread_dump.txt
4. 示例:分析死锁问题
-
问题场景:两个线程互相等待对方的锁,导致死锁。
-
示例代码:
public class DeadlockExample { private static final Object lock1 = new Object(); private static final Object lock2 = new Object(); public static void main(String[] args) { Thread thread1 = new Thread(() -> { synchronized (lock1) { System.out.println("Thread1: Holding lock1..."); try { Thread.sleep(100); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println("Thread1: Waiting for lock2..."); synchronized (lock2) { System.out.println("Thread1: Acquired lock2."); } } }); Thread thread2 = new Thread(() -> { synchronized (lock2) { System.out.println("Thread2: Holding lock2..."); try { Thread.sleep(100); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println("Thread2: Waiting for lock1..."); synchronized (lock1) { System.out.println("Thread2: Acquired lock1."); } } }); thread1.start(); thread2.start(); } }
-
运行后,使用
jstack
查看线程状态:jstack <pid>
-
线程堆栈示例输出:
Found one Java-level deadlock: ============================= "Thread-0": waiting to lock <0x000000071860b408> (a java.lang.Object), which is held by "Thread-1" "Thread-1": waiting to lock <0x000000071860b3d8> (a java.lang.Object), which is held by "Thread-0"
5. 解决方案
- 使用
jstack
定位问题后,可以在代码中:- 避免嵌套锁:减少锁的使用范围。
- 按固定顺序加锁:确保锁获取顺序一致。
- 尝试
tryLock
:使用ReentrantLock
的tryLock
方法,避免死锁。