目录
前言
一、确定需要多个锁的场景
1.独立资源保护
2.部分依赖资源
二、避免死锁
三、锁粒度与并发性能
1. 粗粒度锁定
2.细粒度锁定
四、设计策略:减少资源依赖
1.资源分离
2.无锁设计
3.锁合并
五、Demo讲解
总结:
前言
当多个线程需要操作共享资源时,为了确保数据的一致性和避免竞争条件,通常会使用多个锁来进行同步。这种情况下,如何正确使用多个锁成为一个复杂而关键的问题。下面是一篇十分详细的博客,介绍多线程多锁场景下的最佳实践和注意事项。
一、确定需要多个锁的场景
1.独立资源保护
- 定义:当不同的资源(例如文件、数据库连接等)由不同的锁保护时。
- 示例:一个线程需要读取文件A并写入文件B,而另一个线程读取文件B并写入文件A,这两个操作可以分别使用不同的锁。
2.部分依赖资源
- 定义:多个资源之间存在某种程度的依赖关系,但操作它们的线程可能不会同时访问所有资源。
- 示例:两个线程分别操作两个互相有数据交换的队列,可分别对两个队列加锁,但在交换数据时需要特别小心处理锁的顺序。
二、避免死锁
死锁是多线程编程中常见的问题,特别是在使用多个锁的情况下更容易发生。要避免死锁,可以采取以下策略:
- 按顺序获取锁:对多个资源使用相同的顺序获取锁,以避免循环等待。
- 设置超时时间:在获取锁的过程中设置超时时间,一段时间后未能获取到锁就放弃或重试。
- 使用高级同步工具:比如信号量(Semaphores)或条件变量(Condition Variables),它们提供了更灵活的同步机制,有助于避免死锁。
三、锁粒度与并发性能
1. 粗粒度锁定
- 优点:实现简单,易于理解和维护。
- 缺点:可能导致大量线程等待,从而降低并发性能。
- 示例:一个单一的大锁保护整个资源集合。
2.细粒度锁定
- 优点:提高并发性能,因为锁的范围缩小,减少了线程等待的概率。
- 缺点:实现复杂,需要更精细的设计和管理。
- 示例:为每个独立资源(或资源的部分)使用单独的小锁。
四、设计策略:减少资源依赖
1.资源分离
- 定义:尽量将共享资源划分为独立的部分,使得每个部分只需一个锁。
- 示例:将一个大型数据库拆分为多个独立的部分,每个部分由不同的线程和锁管理。
2.无锁设计
- 定义:通过无锁编程(如使用原子操作)来完全避免锁。
- 示例:使用Java的
AtomicInteger
类进行计数器操作。
3.锁合并
- 定义:在某些情况下,将多个锁合并为一个锁,以简化锁管理。
- 示例:如果两个资源总是一起被访问,可以用一个锁来保护它们。
五、Demo讲解
package com.ctb.demo;
/**
* 关键字synchronized取得的锁都是对象锁,而不是把一段代码(方法)当做锁
* 所以代码中哪个线程先执行synchronized关键字的方法,哪个线程就持有该方法所属对象的锁(Lock),
*
* 在静态方法上加synchronized关键字,表示锁定.class类,类一级别的锁(独占.class类)
*
* @author biao
*
* 2024年
*/
public class MyThread2 {
private int num =0;
public synchronized void printNum(String tag) {
try {
if (tag.equals("a")) {
num=100;
System.out.println("tag a,set num over!");
Thread.sleep(1000);
}else {
num = 200;
System.out.println("tag b,set num over!");
}
System.out.println("tag" + tag + "," + "num" + num);
} catch (Exception e) {
e.printStackTrace();
}
}
public static void main(String[] args) {
final MyThread2 m1 = new MyThread2();
final MyThread2 m2 = new MyThread2();
Thread t1 = new Thread(new Runnable() {
@Override
public void run() {
m1.printNum("a");
}
});
Thread t2 = new Thread(new Runnable() {
@Override
public void run() {
m2.printNum("b");
}
});
t1.start();
t2.start();
}
}
结果:
package com.ctb.demo;
/**
* 关键字synchronized取得的锁都是对象锁,而不是把一段代码(方法)当做锁
* 所以代码中哪个线程先执行synchronized关键字的方法,哪个线程就持有该方法所属对象的锁(Lock),
*
* 在静态方法上加synchronized关键字,表示锁定.class类,类一级别的锁(独占.class类)
*
* @author biao
*
* 2024年2月28日-上午12:07:26
*/
public class MyThread2 {
private static int num =0;
// static
public static synchronized void printNum(String tag) {
try {
if (tag.equals("a")) {
num=100;
System.out.println("tag a,set num over!");
Thread.sleep(1000);
}else {
num = 200;
System.out.println("tag b,set num over!");
}
System.out.println("tag" + tag + "," + "num" + num);
} catch (Exception e) {
e.printStackTrace();
}
}
public static void main(String[] args) {
final MyThread2 m1 = new MyThread2();
final MyThread2 m2 = new MyThread2();
Thread t1 = new Thread(new Runnable() {
@Override
public void run() {
m1.printNum("a");
}
});
Thread t2 = new Thread(new Runnable() {
@Override
public void run() {
m2.printNum("b");
}
});
t1.start();
t2.start();
}
}
结果:
总结:
关键字synchronized取得的锁都是对象锁,而不是把一段代码(方法)当做锁
所以代码中哪个线程先执行synchronized关键字的方法,哪个线程就持有该方法所属对象的锁(Lock),
在静态方法上加synchronized关键字,表示锁定.class类,类一级别的锁(独占.class类)