Appearance
1线程的基本概念
1.1 什么是线程?
线程(Thread)是程序执行流的最小单元,是操作系统能够进行运算调度的最小单位。它被包含在进程之中,是进程中的实际运作单位。
一个进程可以包含多个线程,这些线程共享进程的资源,如内存空间、文件句柄等,但每个线程都有自己独立的程序计数器、寄存器集合和栈空间。
1.2 线程与进程的区别
资源占用:进程拥有独立的地址空间和系统资源,线程共享进程资源
切换开销:线程切换比进程切换开销小,只需要保存和恢复寄存器上下文
通信方式:进程间通信需要特殊的机制,线程间可以直接共享内存
并发度:多线程并发比多进程并发更高效
1.3 线程的生命周期
线程的生命周期包括以下状态:
新建(New):线程对象创建,但尚未启动
就绪(Runnable):线程已启动,等待CPU调度
运行(Running):CPU正在执行线程代码
阻塞(Blocked):线程等待某个条件,暂时停止运行
终止(Terminated):线程执行完毕或异常终止
2Java锁的类型与实现原理
2.1 锁的基本概念
锁是并发编程中用于控制多个线程对共享资源访问的机制,确保同一时间只有一个或有限个线程可以访问共享资源,从而保证数据一致性。
2.2 Java中的锁分类
2.2.1 内置锁(Synchronized)
Synchronized是Java内置的锁机制,它是一种可重入的互斥锁。使用Synchronized可以修饰方法或代码块,确保同一时间只有一个线程可以执行被修饰的代码。
`// 修饰方法
public synchronized void synchronizedMethod() { // 同步代码 }
// 修饰代码块 public void synchronizedBlock() { synchronized(this) { // 同步代码 } }`
2.2.2 Lock接口及其实现
Java 5引入了Lock接口,提供了比Synchronized更灵活的锁机制,包括可中断锁、超时锁、读写锁等。
ReentrantLock:可重入的互斥锁,与Synchronized类似
ReentrantReadWriteLock:读写锁,允许多个线程同时读,只允许一个线程写
StampedLock:改进的读写锁,提供乐观读模式
`// ReentrantLock示例
private final Lock lock = new ReentrantLock();
public void method() { lock.lock(); try { // 临界区代码 } finally { lock.unlock(); } }`
2.3 锁的实现原理
2.3.1 Synchronized的实现
Synchronized在Java 6之前是重量级锁,依赖于操作系统的互斥量实现。Java 6引入了锁升级机制,包括偏向锁、轻量级锁和重量级锁三个阶段,根据竞争情况自动升级。
偏向锁:当只有一个线程访问时,使用偏向锁,减少锁开销
轻量级锁:当有少量线程竞争时,使用CAS操作实现
重量级锁:当竞争激烈时,升级为重量级锁,使用操作系统互斥量
2.3.2 AQS(AbstractQueuedSynchronizer)
AQS是Java并发包中锁机制的基础框架,它提供了一个基于FIFO队列的等待机制,用于实现各种同步器。ReentrantLock、CountDownLatch、Semaphore等都基于AQS实现。
3死锁的形成与检测
3.1 什么是死锁?
死锁是指两个或多个线程在执行过程中,因争夺资源而造成的一种互相等待的现象,若无外力作用,它们都将无法继续执行下去。
3.2 死锁的形成条件
死锁的形成必须同时满足以下四个条件:
互斥条件:资源不能被多个线程同时占用
请求与保持条件:线程持有至少一个资源,同时请求新的资源
不剥夺条件:线程已获得的资源不能被强制剥夺
循环等待条件:多个线程形成头尾相接的循环等待链
3.3 死锁示例
`public class DeadlockExample {
private static final Object lock1 = new Object();
private static final Object lock2 = new Object();
public static void main(String[] args) {
// 线程1:先获取lock1,再获取lock2
Thread thread1 = new Thread(() -> {
synchronized (lock1) {
System.out.println("Thread 1: Holding lock1");
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("Thread 1: Waiting for lock2");
synchronized (lock2) {
System.out.println("Thread 1: Holding lock1 and lock2");
}
}
});
// 线程2:先获取lock2,再获取lock1
Thread thread2 = new Thread(() -> {
synchronized (lock2) {
System.out.println("Thread 2: Holding lock2");
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("Thread 2: Waiting for lock1");
synchronized (lock1) {
System.out.println("Thread 2: Holding lock2 and lock1");
}
}
});
thread1.start();
thread2.start();
}
}`
3.4 死锁的检测与预防
3.4.1 死锁检测
可以通过以下方式检测死锁:
jstack命令:生成线程快照,查看是否存在死锁
JConsole:可视化工具,可检测死锁
程序内部检测:通过算法定期检测资源分配图
3.4.2 死锁预防
预防死锁的方法是破坏死锁的四个必要条件之一:
破坏互斥条件:使用共享资源的副本
破坏请求与保持条件:一次性获取所有资源
破坏不剥夺条件:允许资源被剥夺
破坏循环等待条件:按顺序获取资源
4线程死锁流程解释图
死锁形成过程详解
下图展示了两个线程因争夺资源而形成死锁的完整流程:
图详解:
初始状态:系统中有两个线程(Thread A和Thread B)和两个资源(Resource 1和Resource 2)
线程A获取Resource 1:Thread A成功获取Resource 1的锁
线程B获取Resource 2:Thread B成功获取Resource 2的锁
线程A请求Resource 2:Thread A尝试获取Resource 2,但Resource 2已被Thread B占用,Thread A进入等待状态
线程B请求Resource 1:Thread B尝试获取Resource 1,但Resource 1已被Thread A占用,Thread B进入等待状态
死锁形成:Thread A等待Thread B释放Resource 2,Thread B等待Thread A释放Resource 1,两者互相等待,形成死锁
🔄 交互式死锁模拟
通过以下模拟器,您可以直观地观察死锁的形成过程:
开始模拟
重置
单步执行
自动执行
A
线程A
R1
资源1
R2
资源2
B
线程B
模拟步骤:
初始状态:线程A和线程B准备执行
线程A获取资源1
线程B获取资源2
线程A请求资源2,进入等待状态
线程B请求资源1,进入等待状态
死锁形成:两个线程互相等待
死锁是并发编程中的常见问题,通过理解死锁的形成条件和检测方法,可以有效避免和解决死锁问题。
5死锁的解决方案
5.1 避免死锁的编程实践
按固定顺序获取锁
使用tryLock()尝试获取锁,设置超时时间
减少锁的持有时间
使用并发容器替代同步容器
使用线程池管理线程
5.2 死锁恢复策略
当死锁发生时,可以采取以下恢复策略:
终止线程:终止一个或多个死锁线程,释放资源
资源剥夺:剥夺一个或多个线程的资源,分配给其他线程
进程回滚:将线程回滚到某个安全状态,重新执行
**最佳实践:** 在并发编程中,应尽量避免使用多个锁,如必须使用多个锁,应确保所有线程按相同的顺序获取锁,以避免循环等待。