线程基础
目录
1. 线程的创建方式
1.1 继承Thread类
java
public class MyThread extends Thread {
@Override
public void run() {
System.out.println("线程执行: " + Thread.currentThread().getName());
}
}
// 使用
MyThread thread = new MyThread();
thread.start();面试重点:
- 为什么调用
start()而不是run()?start()会启动新线程,调用run()方法run()只是普通方法调用,不会创建新线程
1.2 实现Runnable接口(推荐)
java
public class MyRunnable implements Runnable {
@Override
public void run() {
System.out.println("线程执行: " + Thread.currentThread().getName());
}
}
// 使用
Thread thread = new Thread(new MyRunnable());
thread.start();
// Lambda表达式方式
Thread thread2 = new Thread(() -> {
System.out.println("Lambda方式创建线程");
});
thread2.start();面试重点:
- 为什么推荐实现Runnable接口?
- Java单继承,实现接口更灵活
- 资源共享:多个线程可以共享同一个Runnable实例
1.3 实现Callable接口
java
public class MyCallable implements Callable<String> {
@Override
public String call() throws Exception {
Thread.sleep(1000);
return "执行结果";
}
}
// 使用
FutureTask<String> futureTask = new FutureTask<>(new MyCallable());
Thread thread = new Thread(futureTask);
thread.start();
// 获取结果
String result = futureTask.get(); // 阻塞等待结果面试重点:
- Callable vs Runnable的区别
- Callable有返回值
- Callable可以抛出异常
- Callable需要配合FutureTask使用
1.4 线程池创建(推荐)
java
ExecutorService executor = Executors.newFixedThreadPool(10);
executor.submit(() -> {
System.out.println("线程池执行任务");
});
executor.shutdown();面试重点:
- 为什么推荐使用线程池?
- 降低资源消耗(线程创建和销毁开销大)
- 提高响应速度(任务到达即可执行)
- 提高线程的可管理性
2. 线程生命周期
线程的6种状态(Thread.State枚举):
- NEW:新建状态,线程被创建但未启动
- RUNNABLE:可运行状态,包括运行中(Running)和就绪(Ready)
- BLOCKED:阻塞状态,等待获取监视器锁
- WAITING:等待状态,无限期等待其他线程的特定操作
- TIMED_WAITING:超时等待状态,在指定时间内等待
- TERMINATED:终止状态,线程执行完毕
状态转换图:
NEW --start()--> RUNNABLE --获取锁--> RUNNABLE
| |
|--wait()--> WAITING --notify()--> RUNNABLE
| |
|--sleep(time)--> TIMED_WAITING --时间到--> RUNNABLE
| |
|--获取锁失败--> BLOCKED --获取锁--> RUNNABLE
| |
--run()结束--> TERMINATED面试重点:
- BLOCKED vs WAITING的区别
- BLOCKED:等待获取synchronized锁
- WAITING:等待其他线程的唤醒操作(wait、join等)
- 如何查看线程状态?
thread.getState()jstack命令
java
Thread thread = new Thread(() -> {
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
});
System.out.println(thread.getState()); // NEW
thread.start();
System.out.println(thread.getState()); // RUNNABLE
Thread.sleep(100);
System.out.println(thread.getState()); // TIMED_WAITING
Thread.sleep(2000);
System.out.println(thread.getState()); // TERMINATED3. 线程同步机制
3.1 synchronized关键字
使用方式:
- 同步代码块
- 同步方法
- 同步静态方法
java
// 1. 同步代码块
public void method() {
synchronized (this) {
// 同步代码
}
}
// 2. 同步实例方法
public synchronized void method() {
// 同步代码
}
// 3. 同步静态方法
public static synchronized void method() {
// 同步代码
}锁的对象:
- 实例方法:锁的是当前对象(this)
- 静态方法:锁的是类对象(Class对象)
- 代码块:锁的是指定的对象
面试重点:
- synchronized的实现原理
- 基于JVM的monitor机制
- 字节码层面:
monitorenter和monitorexit指令 - 对象头中的Mark Word存储锁信息
- synchronized的锁升级过程
- 无锁 → 偏向锁 → 轻量级锁 → 重量级锁
- synchronized vs Lock的区别
- synchronized是关键字,Lock是接口
- synchronized自动释放锁,Lock需要手动释放
- synchronized不可中断,Lock可中断
- synchronized非公平锁,Lock可以公平/非公平
- Lock可以尝试获取锁(tryLock)
锁升级过程详解:
1. 无锁状态:对象刚创建
2. 偏向锁:只有一个线程访问,在对象头记录线程ID
3. 轻量级锁:多个线程竞争,通过CAS获取锁
4. 重量级锁:竞争激烈,线程阻塞,进入等待队列3.2 volatile关键字
作用:
- 可见性:保证变量对所有线程的可见性
- 有序性:禁止指令重排序
java
public class VolatileExample {
private volatile boolean flag = false;
public void writer() {
flag = true; // 写操作
}
public void reader() {
if (flag) { // 读操作
// 操作
}
}
}面试重点:
- volatile的实现原理
- 通过内存屏障(Memory Barrier)实现
- 写操作:强制将工作内存的值刷新到主内存
- 读操作:强制从主内存读取最新值
- volatile vs synchronized
- volatile只能保证可见性和有序性,不能保证原子性
- synchronized可以保证原子性、可见性、有序性
- volatile的使用场景
- 状态标志位
- 双重检查锁定(Double-Checked Locking)
- 单例模式
volatile不能保证原子性的例子:
java
public class VolatileTest {
private volatile int count = 0;
public void increment() {
count++; // 不是原子操作,包含:读取、加1、写入
}
}
// 即使count是volatile,多线程下仍然会出现问题内存屏障:
- LoadLoad:禁止读和读重排序
- StoreStore:禁止写和写重排序
- LoadStore:禁止读和写重排序
- StoreLoad:禁止写和读重排序
4. wait/notify/notifyAll
Object类的方法:
wait():使当前线程等待,释放锁notify():唤醒一个等待的线程notifyAll():唤醒所有等待的线程
使用前提:
- 必须在synchronized代码块中使用
- 必须持有对象的监视器锁
java
public class WaitNotifyExample {
private final Object lock = new Object();
private boolean flag = false;
public void waitMethod() throws InterruptedException {
synchronized (lock) {
while (!flag) { // 使用while而不是if(防止虚假唤醒)
lock.wait(); // 释放锁,进入等待状态
}
// 执行操作
}
}
public void notifyMethod() {
synchronized (lock) {
flag = true;
lock.notify(); // 或 notifyAll()
}
}
}面试重点:
- wait()和sleep()的区别
- wait()是Object的方法,sleep()是Thread的方法
- wait()会释放锁,sleep()不会释放锁
- wait()必须在synchronized中使用,sleep()不需要
- wait()可以被notify()唤醒,sleep()只能等待时间到
- 为什么wait()要在while循环中调用?
- 防止虚假唤醒(Spurious Wakeup)
- 确保条件满足后再继续执行
- notify() vs notifyAll()
- notify():随机唤醒一个线程
- notifyAll():唤醒所有等待的线程
- 一般使用notifyAll()更安全
生产者消费者模式示例:
java
public class ProducerConsumer {
private final Queue<Integer> queue = new LinkedList<>();
private final int MAX_SIZE = 10;
private final Object lock = new Object();
public void produce() throws InterruptedException {
synchronized (lock) {
while (queue.size() == MAX_SIZE) {
lock.wait(); // 队列满,等待
}
queue.offer(1);
lock.notifyAll(); // 唤醒消费者
}
}
public void consume() throws InterruptedException {
synchronized (lock) {
while (queue.isEmpty()) {
lock.wait(); // 队列空,等待
}
queue.poll();
lock.notifyAll(); // 唤醒生产者
}
}
}面试常见问题
线程和进程的区别
- 进程:资源分配的基本单位
- 线程:CPU调度的基本单位
- 一个进程可以包含多个线程
如何停止一个线程?
- 使用标志位(推荐)
- 使用
interrupt()方法 - 不推荐使用
stop()(已废弃)
synchronized的锁升级过程
- 无锁 → 偏向锁 → 轻量级锁 → 重量级锁
volatile的作用
- 保证可见性
- 保证有序性
- 不能保证原子性
最后更新时间:2024年