当前位置:网站首页>并发之多把锁和活跃性
并发之多把锁和活跃性
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,不然就阻塞,此时可以解决死锁问题.
边栏推荐
猜你喜欢
day12---接口和协议
如何使用电子邮件营销在五个步骤中增加产品评论
DSP-ADAU1452输出通道配置
使用pipreqs导出项目所需的requirements.txt(而非整个环境)
How to choose a reliable and formal training institution for the exam in September?
ceph简介
千万级别的表分页查询非常慢,怎么办?
redis AOF持久化个人理解
requests库
- display image API OpenCV 】 【 imshow () to a depth (data type) at different image processing methods
随机推荐
如何使用电子邮件营销在五个步骤中增加产品评论
DeFi明斯基时刻:压力测试与启示
ArcEngine(二)加载地图文档
netstat 及 ifconfig 是如何工作的。
boot-SSE
推荐系统-排序层-模型:Wide&Deep
集群
Karatsuba大数乘法的Verilog实现
服务器资源监控工具-nmon、nmon_analyser
Logic Pro X自带音色库列表
ArcEngine(八)用IWorkspaceFactory加载矢量数据
mysqlbinlog: unknown variable 'default-character-set=utf8'
《剑指Offer》刷题之打印从1到最大的n位数
Mysql如何对两张表的相同字段,同时查询两张数据表
《21天精通TypeScript-5》类型注解与原始类型
进程的创建
[Kaggle combat] Prediction of the number of survivors of the Titanic (from zero to submission to Kaggle to model saving and restoration)
Windows安装MySQL(MIS)
加速FinOps实践,为企业降本增效
ArcEngine(五)用ICommand接口实现放大缩小