多把不相干的锁
一间大屋子有两个功能:睡觉、学习,互不相干。
现在小南要学习,小女要睡觉,但如果只用一间屋子(一个对象锁)的话,那么并发度很低
解决方法就是准备多个房间(对个对象锁)
例如
import cn.itcast.n2.util.Sleeper;
import lombok.extern.slf4j.Slf4j;public class TestMultiLock {public static void main(String[] args) {BigRoom bigRoom = new BigRoom();new Thread(() -> {bigRoom.study();},"小南").start();new Thread(() -> {bigRoom.sleep();},"小女").start();}
}@Slf4j(topic = "c.BigRoom")
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);}}
}
运行结果:(并发度较低)
改进:(加入多把锁)
import cn.itcast.n2.util.Sleeper;
import lombok.extern.slf4j.Slf4j;public class TestMultiLock {public static void main(String[] args) {BigRoom bigRoom = new BigRoom();new Thread(() -> {bigRoom.study();},"小南").start();new Thread(() -> {bigRoom.sleep();},"小女").start();}
}@Slf4j(topic = "c.BigRoom")
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);}}
}
改进后运行结果:用多八把锁/细粒度的锁提高程序的并发性
(需保证业务间无关联)
将锁的粒度细分
class BigRoom {//额外创建对象来作为锁private final Object studyRoom = new Object();private final Object bedRoom = new Object();
}
● 好处:可以增强并发度
● 坏处:如果一个线程需要同时获取多把锁,就容易发生死锁
有这样的情况:一个线程需要同时获取多把锁,这时就容易发生死锁
t1 线程 获得 A对象 锁,接下来想获取 B对象 的锁
t2 线程 获得 B对象 锁,接下来想获取 A对象 的锁
例:各自均有一把锁,都想获取对方锁时会产生死锁,代码最终无法得到执行
import lombok.extern.slf4j.Slf4j;import static cn.itcast.n2.util.Sleeper.sleep;@Slf4j(topic = "c.TestDeadLock")
public class TestDeadLock {public static void main(String[] args) {test1();}private static void test1() {Object A = new Object();Object B = new Object();// t1线程获取A对象上的锁Thread t1 = new Thread(() -> {synchronized (A) {log.debug("lock A");sleep(1);// t1线程经1s后尝试获取锁Bsynchronized (B) {log.debug("lock B");log.debug("操作...");}}}, "t1");// t2线程获取B对象上的锁Thread t2 = new Thread(() -> {synchronized (B) {log.debug("lock B");sleep(0.5);// t2线程经0.5s后尝试获取锁Asynchronized (A) {log.debug("lock A");log.debug("操作...");}}}, "t2");t1.start();t2.start();}
}
死锁在多线程是较常见的。检测死锁可以使用 jconsole
工具,或者使用 jps 定位进程 id,再用 jstack
定位死锁:
①:使用 jps 定位进程 id,再用 jstack
定位
打印死锁信息
②:使用jconsole工具
运行jconsole
监测出出现错误的代码行数
【活跃性】问题,除了死锁以外,还有活锁和饥饿者两种情
况
有五位哲学家,围坐在圆桌旁。
● 他们只做两件事,思考和吃饭,思考一会吃口饭,吃完饭后接着思考。
● 吃饭时要用两根筷子吃,桌上共有 5 根筷子,每位哲学家左右手边各有一根筷子。
● 如果筷子被身边的人拿着,自己就得等待
测试结果:
正常就餐几轮后不能再继续就餐出现无限等待(每个哲学家各持有一根筷子,等待对方放下筷子)
活锁出现在两个线程互相改变对方的结束条件,最后谁也无法结束。
例如
import lombok.extern.slf4j.Slf4j;
import static cn.itcast.n2.util.Sleeper.sleep;@Slf4j(topic = "c.TestLiveLock")
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();}
}
运行结果:(不断在10左右进行加加减减操作,线程失踪无法停止)
死锁与活锁
的区别:
死锁是两个线程持有对方需要的锁,导致线程都无法继续向下运行,两个线程均陷入阻塞;而活锁两个线程均未阻塞,都在使用CPU不断运行,但由于改变对方的结束条件导致两个线程都无法结束
解决活锁的办法
:
● 使两个线程的执行时间有一定的交错(不集中在一起执行/设置睡眠的时间为一个随机数===>将其指令交错开,第一个线程很快运行完,第二个线程将没有机会改变对方的结束条件)
在开发中遇到活锁的情况,可增加随机睡眠时间来避免活锁的产生
很多教程中将饥饿定义为,一个线程由于优先级太低,始终得不到 CPU 调度执行,也不能够结束
,饥饿的情况不易演示,讲读写锁时会涉及饥饿问题
观察一个线程饥饿的例子,可以先观察使用顺序加锁的方式解决之前的死锁问题
相同顺序加锁的解决方案
线程1按AB的顺序加锁,先获得锁A,线程2也是按AB的顺序进行加锁,线程2此时想获取对象A的锁时获取失败(进入对象A的EntryList中阻塞),这时线程1再尝试获取B对象的锁。这样线程1均可以将AB两个锁获取到。等其释放完后线程2也按相同的顺序获取AB对象。
上一篇:ChatGPT没有API?OpenAI官方API带你起飞
下一篇:常用十种算法滤波