Day32 线程安全二
一、线程安全 – ArrayList
1、前言:ArrayList是线程不安全的集合
2、解决方案1:使用Vector – synchronized锁
3、解决方案2:使用Collections的synchronizedList方法将ArrayList转换为线程安全的集合 – synchronized锁
ArrayList<Object> list = new ArrayList<>();
List<Object> synchronizedList = Collections.synchronizedList(list);
4、解决方案3:使用CopyOnWriteArrayList – lock锁
CopyOnWriteArrayList<Object> list = new CopyOnWriteArrayList<>();
list.add("aaa");
list.add("bbb");
list.add("ccc");
list.add("ddd");
5、总结:Vector和synchronizedList()底层使用synchronized(重量级锁),效率很低。项目中推荐使用CopyOnWriteArrayList
二、线程安全 – 死锁
1、概念:
死锁(Deadlock)是指两个或多个线程在互相等待对方释放资源的情况下,导致它们无法继续执行的现象。在死锁情况下,每个线程都在等待另一个线程释放资源,从而导致所有线程都无法继续执行,造成程序无法正常运行。
2、 死锁产生的四个必要条件 :
- 互斥条件:至少有一个资源必须处于非共享模式,即一次只能被一个线程使用。
- 请求与保持条件:线程至少持有一个资源,并且在等待获取其他资源。
- 不剥夺条件:线程已经获得的资源在未使用完之前不能被其他线程抢占。
- 循环等待条件:存在一个循环等待序列,每个线程都在等待下一个线程所持有的资源。
3、 避免死锁的方法 :
- 避免使用多个锁:尽量减少同一时刻持有多个锁的情况,可以考虑使用更高级别的锁机制,如并发集合类。
- 按顺序获取锁:确保线程获取锁的顺序是一致的,避免循环等待的情况。
- 设置超时时间:在获取锁时设置超时时间,避免线程无限等待。
- 死锁检测和恢复:定期检测死锁的发生,并采取相应的恢复措施。
注意:多个线程中的多个锁对象被互相占用
解决方案:尽可能的不要使用锁嵌套
三、线程安全 – 可重入锁
1、概念:
可重入锁(Reentrant Lock)是一种支持重复加锁的锁机制,允许同一个线程多次获取同一把锁,而不会导致死锁。可重入锁在多线程编程中非常有用,可以避免死锁情况,并提供更多灵活性和控制选项。
2、 可重入锁的特点 :
- 重入性:同一个线程可以多次获取同一把锁,而不会被阻塞,确保了线程安全性。
- 公平性:可重入锁可以设置为公平锁或非公平锁,公平锁会按照线程请求的顺序获取锁。
- 可中断性:可重入锁支持可中断的获取锁操作,当线程在等待锁时可以响应中断。
- 超时性:可重入锁支持尝试获取锁的操作,并可以设置超时时间,避免线程无限等待。
- 条件变量:可重入锁支持条件变量,可以通过条件对象实现线程的等待和通知机制。
3、重要性:
在 Java 中,ReentrantLock
是可重入锁的一个常用实现类,提供了上述特性,并且比传统的 synchronized 关键字更加灵活和功能强大。使用可重入锁可以更精细地控制多线程之间的并发访问,避免死锁和提高程序的性能。
4、示例:
import java.util.concurrent.locks.ReentrantLock;
public class ReentrantLockExample {
private ReentrantLock lock = new ReentrantLock();
public void doSomething() {
lock.lock();
try {
// 临界区代码
} finally {
lock.unlock();
}
}
}
代码理解: 在上述示例中,通过 ReentrantLock
实现了可重入锁,使用 lock()
和 unlock()
方法来控制临界区代码的访问。
5、理解:
可重入锁理解:
就是一个线程不用释放,可以重复的获取一个锁n次,只是在释放的时候,也需要相应的释放n次。
简单来说:
A线程在某上下文中获取了某锁,
当A线程想要再次获取该锁时,不会因为锁已经被自己占用,而需要先等到锁的释放。
注意:
synchronized同步代码块是可重入锁
synchronized同步方法是可重入锁
Lock锁是可重入锁