当前位置:网站首页>并发之多把锁和活跃性
并发之多把锁和活跃性
2022-08-03 07:48:00 【七国的天下,我要九十九】
1 多把锁
1 多把不相干锁
举例: 一间大屋子有两个功能:睡觉、学习,互不相干, 现在小明要学习,小红要睡觉,但如果只用一间屋子(一个对象锁)的话,那么并发度很低.
解决办法: 准备多个房间(多个对象锁)
class BigRoom {
public void sleep() {
synchronized (this) {
log.debug("sleeping 2 小时");
Sleeper.sleep(2);
}
}
public void study() {
synchronized (this) {
log.debug("study 1 小时");
Sleeper.sleep(1);
}
}
}
BigRoom bigRoom = new BigRoom();
new Thread(() -> {
bigRoom.compute();
},"小明").start();
new Thread(() -> {
bigRoom.sleep();
},"小红").start();
/* 运行结果: 12:13:54.471 [小明] c.BigRoom - study 1 小时 12:13:55.476 [小红] c.BigRoom - sleeping 2 小时 */
优化
class BigRoom {
private final Object studyRoom = new Object();
private final Object bedRoom = new Object();
public void sleep() {
synchronized (bedRoom) {
log.debug("sleeping 2 小时");
Sleeper.sleep(2);
}
}
public void study() {
synchronized (studyRoom) {
log.debug("study 1 小时");
Sleeper.sleep(1);
}
}
}
/* 运行结果: 12:15:35.069 [小明] c.BigRoom - study 1 小时 12:15:35.069 [小红] c.BigRoom - sleeping 2 小时 */
说明,将锁的粒度细分, 好处是可以增加并发度, 坏处是如果一个线程需要获取多把锁,容易造成死锁
2 活跃性
1 死锁
有下列情况:一个线程需要同时获取多把锁,这时就容易发生死锁:
t1 线程 获得 A对象 锁,接下来想获取 B对象 的锁 t2 线程 获得 B对象 锁,接下来想获取 A对象 的锁.
Object A = new Object();
Object B = new Object();
Thread t1 = new Thread(() -> {
synchronized (A) {
log.debug("lock A");
sleep(1);
synchronized (B) {
log.debug("lock B");
log.debug("操作...");
}
}
}, "t1");
Thread t2 = new Thread(() -> {
synchronized (B) {
log.debug("lock B");
sleep(0.5);
synchronized (A) {
log.debug("lock A");
log.debug("操作...");
}
}
}, "t2");
t1.start();
t2.start();
/* 运行结果: 12:22:06.962 [t2] c.TestDeadLock - lock B 12:22:06.962 [t1] c.TestDeadLock - lock A */
2 定位死锁
检测死锁可以使用 jconsole工具,或者使用 jps 定位进程 id,再用 jstack 定位死锁:
步骤1
cmd > jps
Picked up JAVA_TOOL_OPTIONS: -Dfile.encoding=UTF-8
12320 Jps
22816 KotlinCompileDaemon
33200 TestDeadLock // JVM 进程
11508 Main
28468 Launcher
步骤2
cmd > jstack 33200
...
Found one Java-level deadlock:
=============================
"Thread-1":
waiting to lock monitor 0x000000000361d378 (object 0x000000076b5bf1c0, a java.lang.Object),
which is held by "Thread-0"
"Thread-0":
waiting to lock monitor 0x000000000361e768 (object 0x000000076b5bf1d0, a java.lang.Object),
which is held by "Thread-1"
Java stack information for the threads listed above:
===================================================
"Thread-1":
at thread.TestDeadLock.lambda$main$1(TestDeadLock.java:28)
- waiting to lock <0x000000076b5bf1c0> (a java.lang.Object)
- locked <0x000000076b5bf1d0> (a java.lang.Object)
at thread.TestDeadLock$$Lambda$2/883049899.run(Unknown Source)
at java.lang.Thread.run(Thread.java:745)
"Thread-0":
at thread.TestDeadLock.lambda$main$0(TestDeadLock.java:15)
- waiting to lock <0x000000076b5bf1d0> (a java.lang.Object)
- locked <0x000000076b5bf1c0> (a java.lang.Object)
at thread.TestDeadLock$$Lambda$1/495053715.run(Unknown Source)
at java.lang.Thread.run(Thread.java:745)
Found 1 deadlock.
说明:
- 避免死锁要注意加锁顺序
- 如果由于某个线程进入了死循环,导致其它线程一直等待,对于这种情况 linux 下可以通过 top 先定位到 CPU 占用高的 Java 进程,再利用 top -Hp 进程id 来定位是哪个线程,最后再用 jstack 排查
哲学家就餐问题
有五位哲学家,围坐在圆桌旁。
- 1 他们只做两件事,思考和吃饭,思考一会吃口饭,吃完饭后接着思考。
- 2 吃饭时要用两根筷子吃,桌上共有 5 根筷子,每位哲学家左右手边各有一根筷子。
- 3 如果筷子被身边的人拿着,自己就得等待
筷子类
class Chopstick {
String name;
public Chopstick(String name) {
this.name = name;
}
@Override
public String toString() {
return "筷子{" + name + '}';
}
}
哲学家类
class Philosopher extends Thread {
Chopstick left;
Chopstick right;
public Philosopher(String name, Chopstick left, Chopstick right) {
super(name);
this.left = left;
this.right = right;
}
private void eat() {
log.debug("eating...");
Sleeper.sleep(1);
}
@Override
public void run() {
while (true) {
// 获得左手筷子
synchronized (left) {
// 获得右手筷子
synchronized (right) {
// 吃饭
eat();
}
// 放下右手筷子
}
// 放下左手筷子
}
}
}
吃饭
Chopstick c1 = new Chopstick("1");
Chopstick c2 = new Chopstick("2");
Chopstick c3 = new Chopstick("3");
Chopstick c4 = new Chopstick("4");
Chopstick c5 = new Chopstick("5");
new Philosopher("苏格拉底", c1, c2).start();
new Philosopher("柏拉图", c2, c3).start();
new Philosopher("亚里士多德", c3, c4).start();
new Philosopher("赫拉克利特", c4, c5).start();
new Philosopher("阿基米德", c5, c1).start();
执行一下,就不下去
12:33:15.575 [苏格拉底] c.Philosopher - eating...
12:33:15.575 [亚里士多德] c.Philosopher - eating...
12:33:16.580 [阿基米德] c.Philosopher - eating...
12:33:17.580 [阿基米德] c.Philosopher - eating...
// 运行停止, 不向下运行
使用 jconsole 检测死锁
-------------------------------------------------------------------------
名称: 阿基米德
状态: cn.cf.Chopstick@1540e19d (筷子1) 上的BLOCKED, 拥有者: 苏格拉底
总阻止数: 2, 总等待数: 1
堆栈跟踪:
cn.cf.Philosopher.run(TestDinner.java:48)
- 已锁定 cn.cf.Chopstick@6d6f6e28 (筷子5)
-------------------------------------------------------------------------
名称: 苏格拉底
状态: cn.cf.Chopstick@677327b6 (筷子2) 上的BLOCKED, 拥有者: 柏拉图
总阻止数: 2, 总等待数: 1
堆栈跟踪:
cn.cf.Philosopher.run(TestDinner.java:48)
- 已锁定 cn.cf.Chopstick@1540e19d (筷子1)
-------------------------------------------------------------------------
名称: 柏拉图
状态: cn.cf.Chopstick@14ae5a5 (筷子3) 上的BLOCKED, 拥有者: 亚里士多德
总阻止数: 2, 总等待数: 0
堆栈跟踪:
cn.cf.Philosopher.run(TestDinner.java:48)
- 已锁定 cn.cf.Chopstick@677327b6 (筷子2)
-------------------------------------------------------------------------
名称: 亚里士多德
状态: cn.cf.Chopstick@7f31245a (筷子4) 上的BLOCKED, 拥有者: 赫拉克利特
总阻止数: 1, 总等待数: 1
堆栈跟踪:
cn.cf.Philosopher.run(TestDinner.java:48)
- 已锁定 cn.cf.Chopstick@14ae5a5 (筷子3)
-------------------------------------------------------------------------
名称: 赫拉克利特
状态: cn.cf.Chopstick@6d6f6e28 (筷子5) 上的BLOCKED, 拥有者: 阿基米德
总阻止数: 2, 总等待数: 0
堆栈跟踪:
cn.cf.Philosopher.run(TestDinner.java:48)
- 已锁定 cn.cf.Chopstick@7f31245a (筷子4)
线程没有按预期结束, 暂停执行, 归类为活跃性问题,即 死锁,活锁和饥饿.
3 活锁
活锁出现在两个线程互相改变对方的结束条件,最后谁也无法结束.
public class TestLiveLock {
static volatile int count = 10;
static final Object lock = new Object();
public static void main(String[] args) {
new Thread(() -> {
// 期望减到 0 退出循环
while (count > 0) {
sleep(0.2);
count--;
log.debug("count: {}", count);
}
}, "t1").start();
new Thread(() -> {
// 期望超过 20 退出循环
while (count < 20) {
sleep(0.2);
count++;
log.debug("count: {}", count);
}
}, "t2").start();
}
}
4 饥饿
常见的理解是,一个线程由于优先级太低,始终得不到 CPU 调度执行,也不能够结束.
案例
使用顺序加锁的方式解决之前的死锁问题.
线程1和线程2, 都要同时去获取对象A和对象B锁, 结果线程1获取到对象A锁, 线程2获取到对象B锁, 互相再向下进行, 就互相尝试获取锁,发生死锁.
顺序加锁的解决方案
规定加锁的顺序, 当线程1和线程2,同时竞争对象锁A, 仅有一个线程成功后,才能去竞争对象锁B,不然就阻塞,此时可以解决死锁问题.
边栏推荐
猜你喜欢
word之个人设置
The use of the database table structure document generation tool screw
Postman will return to the interface to generate a json file to the local
品牌方发行NFT时,应如何考量实用性?
Mysql如何对两张表的相同字段,同时查询两张数据表
使用pipreqs导出项目所需的requirements.txt(而非整个环境)
AI中台序列标注任务:三个数据集构造过程记录
Qt5开发从入门到精通——第二篇(控件篇)
【图像边缘检测】基于matlab灰度图像的积累加权边缘检测【含Matlab源码 2010期】
学习Glide 常用场景的写法 +
随机推荐
热部署系统实现
timestamp
Haisi project summary
前缀和(区间和,子矩阵的和)
五、《图解HTTP》报文首部和HTTP缓存
学习Glide 常用场景的写法 +
用云机器/虚拟机架设方舟游戏?
Mysql如何对两张表的相同字段,同时查询两张数据表
xshell开启ssh端口转发,通过公网机器访问内网机器
Roson的Qt之旅#105 QML Image引用大尺寸图片
Eject stubborn hard drives with diskpart's offline command
酷雷曼上新6大功能,全景营销持续加码
【图像边缘检测】基于matlab灰度图像的积累加权边缘检测【含Matlab源码 2010期】
服务器资源监控工具-nmon、nmon_analyser
DSP Trick:向量长度估算
Taro框架-微信小程序-调用微信支付
ArcEngine(八)用IWorkspaceFactory加载矢量数据
Charles packet capture tool learning record
vim 折叠函数
PostMan使用,访问路径@RequestMapping