1.什么是哲学家就餐问题
假如说有五个哲学家,他们坐在一个桌子上同时吃饭,吃完饭后,就思考问题,每个人的左右都有一支筷子总共有五只筷子.
那么如何让这五个人都能够同时吃到饭?
I.问题转换:哲学家就相当于五个线程,同时竞争五个资源(五根筷子相当于五个资源).那么如何使五个线程都能够使用各自的资源从而不产生死锁呢?
2.什么是死锁:
死锁是指两个或两个以上的进程在执行过程中,由于竞争资源或者由于彼此通信而造成的一种阻塞的现象,若无外力作用,他们都将无法推进下去,此时称系统处于死锁状态或系统产生了死锁,这些永远在相互等待的进程称为死锁进程.
这种说法对线程也同样适用
3.死锁产生的四个必要条件:
1.互斥(一个资源同一时间只能被一个线程所使用)
2.请求与保持(一个线程正在请求一个资源,对原来占有的资源保持不放)这是说它自己不放开资源
3.不可剥夺:线程运行过程中,对于已获取的资源,在未使用完之前,不能强行剥夺这是说别的线程不能强行占有它所获得的资源
4.循环等待条件:若干线程之间形成一种头尾相接的循环等待资源关系。
5.以上条件有一条不满足就不会造成死锁
2.举个死锁生活中的例子
3.举个死锁Java代码中的例子
@Slf4j
public class MyTest {
private static Object resource01 = new Object();
private static Object resource02 = new Object();
//因为代码为演示代码没有使用线程池来获取线程
public static void main(String[] args) {
new Thread(() -> {
synchronized (resource01) {
log.info("使用资源1");
try {
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
synchronized (resource02) {
log.info("使用资源2");
}
}
}, "t1").start();
new Thread(() -> {
synchronized (resource02) {
log.info("使用资源2");
try {
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
synchronized (resource01) {
log.info("使用资源1");
}
}
}, "t2").start();
}
}
4.先设计哲学家就餐问题,线程实现
分析:
1.根据面向对象的思维,我们把哲学家看作一个类,筷子看作一个类,
因为是哲学家使用筷子所以哲学家是线程所以要继承thread并且哲学家使用筷子,而且哲学家没有筷子不能吃饭所以哲学家和筷子的关系为关联关系,即筷子类作为哲学家类中一个实例变量.
1.哲学家类:
@Slf4j
public class Philosopher extends Thread {
Chopstick left;
Chopstick right;
String name;
public Philosopher(String name, Chopstick left, Chopstick right) {
super(name);
this.left = left;
this.right = right;
this.name = name;
}
@Override
public void run() {
while (true) {
//尝试获得左手筷子.
log.debug("等待筷子:" + left.name);
synchronized (left) {
System.out.println(name + "获取筷子:" + left.name);
//尝试获得右手筷子
log.debug("等待筷子:" + right.name);
synchronized (right) {
eat(name);
}
}
}
}
private void eat(String name) {
log.debug("eating...");
try {
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
log.debug(name + "吃完了...");
}
}
2.筷子类:
class Chopstick {
String name;
public Chopstick(String name) {
this.name = name;
}
@Override
public String toString() {
return "筷子{" + name + "}";
}
}
3.进行测试:
class TestDeadLock {
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");
/**
* 1 2
* 2 3
* 3 4
* 4 5
* 1 5
*
* 1 -->等待2 2--->等待3 3-->等待4 4-->等待5 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();
}
}
分析:此时会产生死锁.因为我们看,因为加了synchronizedc1所以满足:互斥,请求与保持,不可剥夺,又因为从代码中分析c1等待c2 , c2 在等待c3 ,c3在等待c4c4在等待c5,c5在等待c1 所以满足死锁的四个充分条件.
5.分析解决哲学家就餐的死锁问题
分析1:
1.因为只要不满足这四个条件的任意一个都不会产生死锁问题
2.我们先来想一个最简单的做法,从代码中可以看到c1等待c2 ,c2 在等待c3 ,c3在等待c4c4在等待c5,c5在等
待c1.所以我们只需破坏循环等待条件给他们吃饭加个限制,比如说我们不让阿基米德先拿第五根筷子,我 们也是先让它拿第一根筷子然后就破环了循环等待的条件.就不会产生死锁了.
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");
/**
* 1 2
* 2 3
* 3 4
* 4 5
* 1 5
*
* 1 -->等待2 2--->等待3 3-->等待4 4-->等待5 5不做等待
*
*/
new Philosopher("苏格拉底", c1, c2).start();
new Philosopher("柏拉图", c2, c3).start();
new Philosopher("亚里士多德", c3, c4).start();
new Philosopher("赫拉克利特", c4, c5).start();
new Philosopher("阿基米德", c1, c5).start();
分析2:
首先我们要分析能不能破环互斥条件,假如说我们破坏了互斥条件,那么在多线程访问的过程中就会产生指令的交错产生线程不安全.所以这个我们可以先排除.
其次我们可不可以破坏不可剥夺和请求与保持条件呢?这两个条件的支撑是因为加了synchronized锁,
synchronized锁保证了没有执行完就不能释放资源且资源只能被占用.因为synchronized是jvm底层实现的所以说我们不能在synchronized上做文章,那么有没有能代替synchronized的锁并且还能够不满足时释放资源,而且还能保证互斥条件呢?当然有ReentrantLock 下面就让我们使用ReentrantLock来解决这个问题吧.
@Slf4j
class Philosophers extends Thread {
Chopsticks left;
Chopsticks right;
public Philosophers(String name, Chopsticks left, Chopsticks right) {
super(name);
this.left = left;
this.right = right;
}
@Override
public void run() {
while (true) {
/* synchronized (left) {
synchronized (right) {
eat();
}
}*/
if (left.tryLock()) {
try {
if (right.tryLock()) {
try {
eat();
} finally {
right.unlock();
}
}
} finally {
left.unlock();
}
}
}
}
private void eat() {
log.debug("吃饭....");
try {
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
log.debug("吃完了...");
}
}
class Chopsticks extends ReentrantLock {
String name;
public Chopsticks(String name) {
this.name = name;
}
@Override
public String toString() {
return "Chopsticks{" +
"name='" + name + '\'' +
'}';
}
}