本文正在参加「Java主题月 - Java Debug笔记活动」,详情查看 活动链接
前言
本教程中了例子参考了Java高并发编程(第二版)——葛一鸣
一、锁是什么?
锁一般用于并发编程中保护临界区资源的 举个🌰,同时有多个人去商店买东西,商店中的东西就相当于临界资源,每个人看作一个线程。当某个人去买某件东西时,其他人无法购买的。直到那个人把东西买走释放了锁,商品数量-1,其他人可继续购买。
锁又分为乐观锁和悲观锁
- 乐观锁认为读多写少,多采用CAS(compare and set)来实现的
- 悲观锁认为写多读少,遇到并发写的可能性较高。例如synchronized关键字就是用的悲观锁
二、死锁
1.哲学家就餐
假设有五个哲学家👨,他们的生活方式是交替地进行思考和进餐,哲学家们共用一张圆桌,分别坐在周围的五张椅子上,在圆桌上有五个碗和五支🥢,平时哲学家进行思考,饥饿时便试图取其左、右最靠近他的🥢,只有在他拿到两支筷子时才能进餐,该哲学家进餐完毕后,放下左右两只筷子又继续思考。
约束条件
(1)只有拿到两只🥢时,哲学家才能吃饭。
(2)如果筷子已被别人拿走,则必须等别人吃完之后才能拿到筷子。
(3)任一哲学家在自己未拿到两只筷子吃完饭前,不会放下手中已经拿到的筷子。
代码如下(示例):
public class DeadLock extends Thread {
// 5个筷子
static Object[] tools = {"k1", "k2", "k3", "k4", "k5"};
// 位置
private int i;
public DeadLock(int i) {
this.i = i;
this.setName("哲学家" + i);
}
@Override
public void run() {
// 先取左筷子
System.out.println(getName() + "准备吃饭了");
synchronized (tools[i -1]) {
try {
Thread.sleep(50);
// 再取右筷子
synchronized (tools[i ==5 ? 0:i]) {
Thread.sleep(500);
System.out.println(getName() + "吃完了");
}
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
public static void main(String[] args) throws InterruptedException {
DeadLock d1 = new DeadLock(1);
DeadLock d2 = new DeadLock(2);
DeadLock d3 = new DeadLock(3);
DeadLock d4 = new DeadLock(4);
DeadLock d5 = new DeadLock(5);
d1.start();
d2.start();
d3.start();
d4.start();
d5.start();
Thread.sleep(1000);
}
}
一组可能的输出结果如下:
哲学家1准备吃饭了
哲学家3准备吃饭了
哲学家5准备吃饭了
哲学家4准备吃饭了
哲学家2准备吃饭了
很明显此时发生了死锁,每个哲学家都有左筷子,但是无法拿到右筷子。 此时可以通过命令来查看当前Java进程状态
- jps
一组可能的输出结果如下:
12613 Jps
12551 DeadLock
3991 Launcher
12552 Launcher
6829 KotlinCompileDaemon
7421 Main
- jstack 12551
一组可能的输出结果如下:
......
"哲学家1" #12 prio=5 os_prio=0 cpu=4.44ms elapsed=53.88s tid=0x00007efeac462800 nid=0x3130 waiting for monitor entry [0x00007efe7a839000]
java.lang.Thread.State: BLOCKED (on object monitor)
at com.jackbin.concurrent.chapter04.DeadLock.run(DeadLock.java:26)
- waiting to lock <0x0000000093bed0c8> (a java.lang.String)
- locked <0x0000000093bed098> (a java.lang.String)
"哲学家2" #13 prio=5 os_prio=0 cpu=3.73ms elapsed=53.88s tid=0x00007efeac464800 nid=0x3131 waiting for monitor entry [0x00007efe7a738000]
java.lang.Thread.State: BLOCKED (on object monitor)
at com.jackbin.concurrent.chapter04.DeadLock.run(DeadLock.java:26)
- waiting to lock <0x0000000093bed0f8> (a java.lang.String)
- locked <0x0000000093bed0c8> (a java.lang.String)
"哲学家3" #14 prio=5 os_prio=0 cpu=3.96ms elapsed=53.88s tid=0x00007efeac466000 nid=0x3132 waiting for monitor entry [0x00007efe7a637000]
java.lang.Thread.State: BLOCKED (on object monitor)
at com.jackbin.concurrent.chapter04.DeadLock.run(DeadLock.java:26)
- waiting to lock <0x0000000093bed128> (a java.lang.String)
- locked <0x0000000093bed0f8> (a java.lang.String)
......
2.防止死锁
此处使用ReentrantLock重入锁来防止死锁 具体方案为,如果再5S内拿不到右筷子,则放弃手中的的左筷子,并重新请求
代码如下:
public class DeadLock extends Thread {
// 5个筷子
static ReentrantLock[] tools = {
new ReentrantLock(),
new ReentrantLock(),
new ReentrantLock(),
new ReentrantLock(),
new ReentrantLock()
};
// 位置
private int i;
public DeadLock(int i) {
this.i = i;
this.setName("哲学家" + i);
}
@Override
public void run() {
try {
while (true) {
System.out.println(getName() + "准备吃饭了");
// 先取左筷子
tools[i-1].lock();
Thread.sleep(50);
// 如果5s未取到右筷子,则放弃
if (tools[i ==5 ? 0:i].tryLock(5, TimeUnit.SECONDS)) {
Thread.sleep(500);
System.out.println(getName() + "吃完了");
// 解锁
tools[i-1].unlock();
tools[i ==5 ? 0:i].unlock();
break;
} else {
System.out.println(getName() + "未取到右筷子,故释放左筷子");
tools[i-1].unlock();
}
}
} catch (InterruptedException e) {
e.printStackTrace();
}
}
public static void main(String[] args) throws InterruptedException {
DeadLock d1 = new DeadLock(1);
DeadLock d2 = new DeadLock(2);
DeadLock d3 = new DeadLock(3);
DeadLock d4 = new DeadLock(4);
DeadLock d5 = new DeadLock(5);
d1.start();
d2.start();
d3.start();
d4.start();
d5.start();
Thread.sleep(1000);
}
}
一组可能的输出结果如下:
哲学家2准备吃饭了
哲学家4准备吃饭了
哲学家1准备吃饭了
哲学家5准备吃饭了
哲学家3准备吃饭了
哲学家2未取到右筷子,故释放左筷子
哲学家2准备吃饭了
哲学家5未取到右筷子,故释放左筷子
哲学家5准备吃饭了
哲学家4未取到右筷子,故释放左筷子
哲学家4准备吃饭了
哲学家3未取到右筷子,故释放左筷子
哲学家3准备吃饭了
哲学家1未取到右筷子,故释放左筷子
哲学家1准备吃饭了
哲学家2未取到右筷子,故释放左筷子
哲学家2准备吃饭了
哲学家5未取到右筷子,故释放左筷子
哲学家5准备吃饭了
哲学家3未取到右筷子,故释放左筷子
哲学家3准备吃饭了
哲学家1未取到右筷子,故释放左筷子
哲学家1准备吃饭了
哲学家4吃完了
哲学家3吃完了
哲学家2吃完了
哲学家1吃完了
哲学家5吃完了
总结
以上就是我对锁的一些理解🌶,多线程🐮🐸~