多把不相干的锁
一间大屋子有两个功能:睡觉、学习,互不相干。
现在小南要学习,小女要睡觉,但如果只用一间屋子(一个对象锁)的话,那么并发度很低
解决方法是准备多个房间(多个对象锁),即缩小锁的粒度
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);
}
}
}
锁的粒度细分
好处,是可以增强并发度
坏处,如果一个线程需要同时获得多把锁,就容易发生死锁
前提:两把锁锁住的两段代码互不相关
活跃性
死锁
有这样的情况:一个线程需要同时获取多把锁,这时就容易发生死锁
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
活锁
活锁出现在两个线程互相改变对方的结束条件,最后谁也无法结束,例如
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();
}
}
解决方式:
错开线程的运行时间,使得一方不能改变另一方的结束条件。
将睡眠时间调整为随机数。
饥饿
很多教程中把饥饿定义为,一个线程由于优先级太低,始终得不到 CPU 调度执行,也不能够结束,饥饿的情况不易演示,讲读写锁时会涉及饥饿问题
下面我讲一下我遇到的一个线程饥饿的例子,先来看看使用顺序加锁的方式解决之前的死锁问题
饥饿
很多教程中把饥饿定义为,一个线程由于优先级太低,始终得不到 CPU 调度执行,也不能够结束,饥饿的情况不易演示,讲读写锁时会涉及饥饿问题
下面我讲一下我遇到的一个线程饥饿的例子,先来看看使用顺序加锁的方式解决之前的死锁问题
说明:
顺序加锁可以解决死锁问题,但也会导致一些线程一直得不到锁,产生饥饿现象。
解决方式:ReentrantLock