一起养成写作习惯!这是我参与「掘金日新计划 · 4 月更文挑战」的第11天,点击查看活动详情。
多把锁
一间大屋子有两个功能:睡觉、学习,互不相干。
现在小南要学习,小女要睡觉,但如果只用一间屋子(一个对象锁)的话,那么并发度很低
解决方法是准备多个房间(多个对象锁) 例如
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();
结果:
改进:
将锁的粒度细分
-
好处,是可以增强并发度
-
坏处,如果一个线程需要同时获得多把锁,就容易发生死锁
死锁
有这样的情况:一个线程需要同时获取多把锁,这时就容易发生死锁
t1 线程 获得 A对象 锁,接下来想获取 B对象 的锁 t2 线程 获得 B对象 锁,接下来想获取 A对象 的锁 例:
定位死锁
- 检测死锁可以使用 jconsole工具,或者使用 jps 定位进程 id,再用 jstack 定位死锁:
-
避免死锁要注意加锁顺序
-
另外如果由于某个线程进入了死循环,导致其它线程一直等待,对于这种情况 linux 下可以通过 top 先定位到CPU 占用高的 Java 进程,再利用 top -Hp 进程id 来定位是哪个线程,最后再用 jstack 排查
哲学家就餐问题
有五位哲学家,围坐在圆桌旁。
-
他们只做两件事,思考和吃饭,思考一会吃口饭,吃完饭后接着思考。
-
吃饭时要用两根筷子吃,桌上共有 5 根筷子,每位哲学家左右手边各有一根筷子。
-
如果筷子被身边的人拿着,自己就得等待
筷子类
class Chopstick {
String name;
public Chopstick(String name) {
this.name = name;
}
@Override
public String toString() {
return "筷子{" + name + '}';
}
}
哲学家类
@Slf4j(topic = "c.Philosopher")
class Philosopher extends Thread {
Chopstick left;
Chopstick right;
public Philosopher(String name, Chopstick left, Chopstick right) {
super(name);
this.left = left;
this.right = right;
}
@Override
public void run() {
while (true) {
// 尝试获得左手筷子
synchronized (left) {
// 尝试获得右手筷子
synchronized (right) {
eat();
}
}
}
}
Random random = new Random();
private void eat() {
log.debug("eating...");
Sleeper.sleep(0.5);
}
}
就餐
public static void main(String[] args) {
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();
}
会发现执行多次之后卡住无法向下执行
使用jconsole检查死锁
名称: 柏拉图
状态: cn.itcast.n4.deadlock.v1.Chopstick@407a59ea上的BLOCKED, 拥有者: 亚里士多德
总阻止数: 2, 总等待数: 0
堆栈跟踪:
cn.itcast.n4.deadlock.v1.Philosopher.run(TestDeadLock.java:41)
- 已锁定 cn.itcast.n4.deadlock.v1.Chopstick@513e2c4d
名称: 苏格拉底
状态: cn.itcast.n4.deadlock.v1.Chopstick@513e2c4d上的BLOCKED, 拥有者: 柏拉图
总阻止数: 5, 总等待数: 6
堆栈跟踪:
cn.itcast.n4.deadlock.v1.Philosopher.run(TestDeadLock.java:41)
- 已锁定 cn.itcast.n4.deadlock.v1.Chopstick@118b2d80
名称: 柏拉图
状态: cn.itcast.n4.deadlock.v1.Chopstick@407a59ea上的BLOCKED, 拥有者: 亚里士多德
总阻止数: 2, 总等待数: 0
堆栈跟踪:
cn.itcast.n4.deadlock.v1.Philosopher.run(TestDeadLock.java:41)
- 已锁定 cn.itcast.n4.deadlock.v1.Chopstick@513e2c4d
名称: 亚里士多德
状态: cn.itcast.n4.deadlock.v1.Chopstick@16faf7d6上的BLOCKED, 拥有者: 赫拉克利特
总阻止数: 8, 总等待数: 8
堆栈跟踪:
cn.itcast.n4.deadlock.v1.Philosopher.run(TestDeadLock.java:41)
- 已锁定 cn.itcast.n4.deadlock.v1.Chopstick@407a59
名称: 赫拉克利特
状态: cn.itcast.n4.deadlock.v1.Chopstick@26c4e013上的BLOCKED, 拥有者: 阿基米德
总阻止数: 2, 总等待数: 0
堆栈跟踪:
cn.itcast.n4.deadlock.v1.Philosopher.run(TestDeadLock.java:41)
- 已锁定 cn.itcast.n4.deadlock.v1.Chopstick@16faf7d6
这种线程没有按预期结束,执行不下去的情况,归类为【活跃性】问题,除了死锁以外,还有活锁和饥饿者两种情况