多线程编程与线程机制笔记
线程分类
- 用户线程:由用户自定义的线程,通常执行应用逻辑。
- 守护线程:运行在后台的特殊线程,例如垃圾回收线程。
多线程编程步骤
-
创建资源类:
- 资源类应包含操作方法,设计上保持高内聚低耦合。
-
资源类中的操作方法:
- 判断:检查条件,决定是否需要等待。
- 干活:执行具体任务。
- 通知:完成后通知其他线程。
- 创建线程:通过线程调用资源类的操作方法。
-
防止虚假唤醒问题:使用
while
循环代替if
条件检查。
Lock 与 synchronized 对比
Lock 特点
- 是一个接口,而
synchronized
是关键字。 - 提供更强大的功能,如可重入锁、条件变量等。
- 手动加锁和解锁,灵活性高。
两者区别
-
异常处理:
-
synchronized
发生异常时会自动释放锁,不会导致死锁。 -
Lock
不会自动释放锁,需手动释放,否则可能引发死锁。
-
-
性能与适用场景:
-
Lock
更适合高并发场景,提供非阻塞的尝试加锁机制。 -
synchronized
更简洁,适合简单同步。
-
虚假唤醒问题
虚假唤醒是由于 wait
方法的特点引起的,线程可能在没有满足条件的情况下被唤醒。
修正方案:用 while
循环替代 if
条件。
public synchronized void decreament() throws InterruptedException {
// 判断
while (number != 1) {
this.wait();
}
number--;
System.out.println(Thread.currentThread() + " :: " + number);
this.notifyAll(); // 通知其他线程
}
线程的定制化通信
案例:线程交替打印
需求:A 线程打印 5 次,B 线程打印 10 次,C 线程打印 15 次,循环 10 轮。
实现方案
- 每个线程有一个对应的标志位(flag):1、2、3。
- 每次操作后修改标志位,并通知下一个线程继续执行。
Java 集合的线程安全问题
线程不安全的集合
-
ArrayList
:- 替代方案:
Vector
- 工具类方法:
Collections.synchronizedList
-
CopyOnWriteArrayList
:- 使用写时复制技术,支持并发读,独立写入。
- 替代方案:
-
HashSet
:- 替代方案:
CopyOnWriteArraySet
- 替代方案:
-
HashMap
:- 替代方案:
ConcurrentHashMap
- 替代方案:
synchronized 用法
- 同步方法:锁住实例对象。
-
静态同步方法:锁住类的
Class
对象。 -
同步代码块:
synchronized
锁定括号内指定的对象。
锁的分类
公平锁与非公平锁
- 公平锁:线程按照请求锁的顺序获得锁(性能较低)。
- 非公平锁:线程可以插队获得锁(性能较高,但可能导致线程饿死)。
可重入锁(递归锁)
-
synchronized
和Lock
都是可重入锁。 -
特点:
- 同一个线程获取锁后,可以多次进入同一个锁保护的代码块。
-
synchronized
是隐式实现,Lock
需要显式加锁和解锁。
死锁
定义
死锁是指两个或多个线程因为资源竞争而相互等待,导致程序无法继续执行的现象。
死锁产生的原因
- 系统资源不足。
- 线程获取锁的顺序不合适。
- 资源分配方式不当。
如何检测死锁
-
使用
jps
命令:- 列出所有 JVM 进程。
-
使用
jstack
命令:- 配合进程号查看线程堆栈,定位死锁问题。
代码示例
死锁代码示例
public class DeadlockExample {
private static final Object lock1 = new Object();
private static final Object lock2 = new Object();
public static void main(String[] args) {
Thread t1 = new Thread(() -> {
synchronized (lock1) {
System.out.println("Thread 1: Holding lock1...");
try { Thread.sleep(100); } catch (InterruptedException e) {}
synchronized (lock2) {
System.out.println("Thread 1: Acquired lock2...");
}
}
});
Thread t2 = new Thread(() -> {
synchronized (lock2) {
System.out.println("Thread 2: Holding lock2...");
try { Thread.sleep(100); } catch (InterruptedException e) {}
synchronized (lock1) {
System.out.println("Thread 2: Acquired lock1...");
}
}
});
t1.start();
t2.start();
}
}
总结
-
Lock
与synchronized
的选择:- 简单场景用
synchronized
,复杂并发用Lock
。
- 简单场景用
-
死锁检测与避免:
- 注意锁获取顺序。
- 使用工具如
jstack
定位问题。
-
虚假唤醒:
- 使用
while
包裹判断条件。
- 使用
-
集合线程安全:
- 使用线程安全的集合,如
ConcurrentHashMap
或CopyOnWriteArrayList
。
- 使用线程安全的集合,如