并发学习记录06:多把锁
锁的粒度减小,并发量也会增大,当然也会随之而来一些问题
示例
假如有一个大房子有两个功能:睡觉和学习,互不相干,t1线程要学习,t2线程要睡觉,如果都用一个房子的话,并发度很低,解决方法就是用多个房子
就用一个房子
public class Test01 {
public static void main(String[] args) {
BigRoom bigRoom = new BigRoom();
new Thread(() -> {
bigRoom.study();
}, "t1").start();
new Thread(() -> {
bigRoom.sleep();
}, "t2").start();
}
}
@Slf4j(topic = "ch.BigRoom")
class BigRoom {
public void sleep() {
synchronized (this) {
log.debug("sleeping 2 hour");
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
public void study() {
synchronized (this) {
log.debug("study 1 hour");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
将房子划分为两个锁
public class Test02 {
public static void main(String[] args) {
BigRoom02 bigRoom02 = new BigRoom02();
new Thread(() -> {
bigRoom02.study();
}, "t1").start();
new Thread(() -> {
bigRoom02.sleep();
}, "t2").start();
}
}
//两个方法不公用一个锁了
@Slf4j(topic = "ch.BigRoom02")
class BigRoom02 {
private final Object studyRoom = new Object();
private final Object bedRoom = new Object();
public void sleep() {
synchronized (bedRoom) {
log.debug("睡觉2s");
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
public void study() {
synchronized (studyRoom) {
log.debug("学习1s");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
锁粒度变小好处是可以增加并发度,坏处就是容易发生死锁
活跃性
死锁
如果存在这样的情况,一个线程需要同时获取多把锁,这时就容易发生死锁
假如:t1线程获取a对象锁,接下来想获取b对象锁,t2线程获取了b对象的锁,接下来想获取a对象的锁,这时候就可能会出现死锁
代码:
@Slf4j(topic = "ch.SiSuoTest01")
public class SiSuoTest01 {
public static void main(String[] args) {
Object a = new Object();
Object b = new Object();
new Thread(() -> {
synchronized (a) {
log.debug("已经获得a,想要获得b");
synchronized (b) {
log.debug("已经获得b");
}
}
log.debug("结束");
}, "t1").start();
new Thread(() -> {
synchronized (b) {
log.debug("已经获得b,想要获得a");
synchronized (a) {
log.debug("已经获得a");
}
}
log.debug("结束");
}, "t2").start();
}
}
定位死锁
检测死锁可以用jconslole工具,或者使用jps定位进程id,然后再用jstack定位死锁
D:\JAVAPROJECT\JUCdemo>jps
23152 TestWaitNotify04
20724
14988 SiSuoTest01
18396 Launcher
6092 Jps
//这里要查看的是SiSuoTest01的信息
D:\JAVAPROJECT\JUCdemo>jstack 14988
找到死锁信息
Found one Java-level deadlock:
=============================
"t2":
waiting to lock monitor 0x000000001c450e88 (object 0x000000076d09e298, a java.lang.Object),
which is held by "t1"
"t1":
waiting to lock monitor 0x000000001f434938 (object 0x000000076d09e2a8, a java.lang.Object),
which is held by "t2"
Java stack information for the threads listed above:
===================================================
"t2":
at Cha01Thread.siSuo.SiSuoTest01.lambda$main$1(SiSuoTest01.java:23)
- waiting to lock <0x000000076d09e298> (a java.lang.Object)
- locked <0x000000076d09e2a8> (a java.lang.Object)
at Cha01Thread.siSuo.SiSuoTest01$$Lambda$2/245672235.run(Unknown Source)
at java.lang.Thread.run(Thread.java:748)
"t1":
at Cha01Thread.siSuo.SiSuoTest01.lambda$main$0(SiSuoTest01.java:14)
- waiting to lock <0x000000076d09e2a8> (a java.lang.Object)
- locked <0x000000076d09e298> (a java.lang.Object)
at Cha01Thread.siSuo.SiSuoTest01$$Lambda$1/1321640594.run(Unknown Source)
at java.lang.Thread.run(Thread.java:748)
Found 1 deadlock.
避免死锁需要注意加锁顺序
如果某个线程进入了死循环,导致其他线程一直等待,对于这种情况linux可以通过top先定位到CPU占用高的java进程,再利用top -Hp 进程id定位是哪个线程,最后用jstack进行排查
活锁
活锁出现在两个线程互相改变对方的结束条件,最后谁也无法结束,例如:
@Slf4j(topic = "ch.TestLiveLock")
public class TestLiveLock {
static volatile int count = 10;
static final Object lock = new Object();
public static void main(String[] args) {
new Thread(() -> {
while (count > 0) {
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
count--;
log.debug("count:{}", count);
}
}, "t1").start();
new Thread(() -> {
while (count < 20) {
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
count++;
log.debug("count:{}", count);
}
}, "t1").start();
}
}
饥饿
饥饿:一个线程由于优先级太低,始终得不到cpu调度执行,也不能够结束,比如有多个读进程和一个写进程,读锁只要加一次,维护一个count变量知道现在有多少个读进程在读数据,读进程首先对数据加了锁,在并发调度时,由于其他读进程只要对count变量进行加1,就会一直读这个数据,写进程就迟迟无法上写锁,这个情况的写进程就是饥饿
· 分享一个免费、快速、无限量使用的满血 DeepSeek R1 模型,支持深度思考和联网搜索!
· 使用C#创建一个MCP客户端
· ollama系列1:轻松3步本地部署deepseek,普通电脑可用
· 基于 Docker 搭建 FRP 内网穿透开源项目(很简单哒)
· 按钮权限的设计及实现