Java 经典多线程问题(二)死锁问题

Java 经典多线程问题(一)生产者消费者问题
Java 经典多线程问题(二)死锁问题

一、线程的死锁

线程死锁的必要条件:
(1) 互斥条件: 资源只能被一个或有限个线程使用。
(2) 请求与保持条件: 当进程在申请资源被阻塞时,不释放自己拥有的资源。
(3) 不剥夺条件: 进程已拥有的资源不能强行剥夺。
(4) 循环等待条件: 若干进程之间形成一种头尾相接的循环等待资源关系。

要出现死锁,以上四个必要条件缺一不可。所以,解决死锁问题,就是破坏以上四个条件的任何一个,实际解决时应根据业务需求适当选取。

注意:发生死锁时一定具备上述四个条件,但具备上述四个条件时,不一定会发生死锁。

二、哲学家就餐问题

哲学家就餐问题是死锁的一个经典问题。

情境模拟:在一个圆桌上,有n个哲学家,n只筷子,每个哲学家左右两边各返一只筷子。
                     哲学家可以进行思考和吃饭,思考时,不获取筷子。
                     吃饭时,必须同时获得左右两只筷子才能吃饭

三、死锁时的哲学家就餐代码:

/**
 * 哲学家类
 */
public class Philosopher implements Runnable{

    // 左边的筷子
    private Chopstick leftChopstick;
    // 右边的筷子
    private Chopstick rightChopstick;
    // 哲学家名字
    private String name;

    public Philosopher( Chopstick leftChopstick,  Chopstick rightChopstick, String name) {
        this.leftChopstick = leftChopstick;
        this.rightChopstick = rightChopstick;
        this.name = name;
    }

    @Override
    public void run() {
            eat();
            think();
    }

    public void eat(){
        System.out.println(name + "饥饿");
        leftChopstick.requestUse(name);
        rightChopstick.requestUse(name);
        try {
            Thread.sleep((long) (Math.random() * 1000));
        } catch (InterruptedException e) {
            e.printStackTrace();;
        }
        leftChopstick.giveupUse(name);
        rightChopstick.giveupUse(name);
    }

    public void think() {
        try {
            Thread.sleep((long) (Math.random() * 1000));
        } catch (InterruptedException e) {
            e.printStackTrace();;
        }
    }
}
/**
 * 筷子类
 */
public class Chopstick {

    // 筷子的状态
    private boolean isUsing = false;
    // 筷子名
    private String name;

    public Chopstick(String name){
        this.name = name;
    }

    // 请求使用筷子
    public synchronized void requestUse(String usrName) {
        try {
            if(isUsing){
                System.out.println(usrName + "申请" + name + ",被阻塞");
                wait();
            }


            isUsing = true;
            System.out.println(usrName + "申请" + name + ",成功!");
        } catch (InterruptedException e) {
            e.printStackTrace();
        }    
    }

    // 放弃筷子
    public synchronized void giveupUse(String usrName) {
        isUsing = false;
        System.out.println(usrName + "吃完,放弃" + name + ",并唤醒所有等待的哲学家");
        notifyAll();
    }
}
/**
 * 可能死锁的情况
 */
public class DeadlockTest {

    public static void main(String[] args) {
        List<Chopstick> chopsticks = new ArrayList<>();
        for (int i = 0; i < 3; i++) {
            chopsticks.add(new Chopstick("筷子" + (i + 1)));
        }
        Philosopher philosopher1 = new Philosopher(chopsticks.get(0), chopsticks.get(1), "哲学家1");
        Philosopher philosopher2 = new Philosopher(chopsticks.get(1), chopsticks.get(2), "哲学家2");
        Philosopher philosopher3 = new Philosopher(chopsticks.get(2), chopsticks.get(0), "哲学家3");
        new Thread(philosopher2).start();
        new Thread(philosopher1).start();
        new Thread(philosopher3).start();
    }
}

四、测试的结果:

没有发生死锁的情况:

哲学家2饥饿
哲学家3饥饿
哲学家3申请筷子3,成功!
哲学家3申请筷子1,成功!
哲学家1饥饿
哲学家1申请筷子1,被阻塞
哲学家2申请筷子2,成功!
哲学家2申请筷子3,被阻塞
哲学家3吃完,放弃筷子3,并唤醒所有等待的哲学家
哲学家3吃完,放弃筷子1,并唤醒所有等待的哲学家
哲学家2申请筷子3,成功!
哲学家1申请筷子1,成功!
哲学家1申请筷子2,被阻塞
哲学家2吃完,放弃筷子2,并唤醒所有等待的哲学家
哲学家2吃完,放弃筷子3,并唤醒所有等待的哲学家
哲学家1申请筷子2,成功!
哲学家1吃完,放弃筷子1,并唤醒所有等待的哲学家
哲学家1吃完,放弃筷子2,并唤醒所有等待的哲学家

发生死锁的情况:

哲学家2饥饿
哲学家3饥饿
哲学家1饥饿
哲学家3申请筷子3,成功!
哲学家2申请筷子2,成功!
哲学家1申请筷子1,成功!
哲学家2申请筷子3,被阻塞
哲学家3申请筷子1,被阻塞
哲学家1申请筷子2,被阻塞

五、解决问题:

破坏必要条件,
思路一:可以在线程被阻塞时释放已经申请到的筷子,这样破坏了请求与保持条件。
思路二:可以改变某个哲学家的申请顺序,破坏循环等待条件。

   下面根据思路二,对上面的代码做修改:

/**
 * 哲学家类
 */
public class Philosopher implements Runnable{

    // 左边的筷子
    private Chopstick leftChopstick;
    // 右边的筷子
    private Chopstick rightChopstick;
    // 哲学家名字
    private String name;
    // 淘气的一个:不按顺序申请
    private boolean taoQi;

    public Philosopher( Chopstick leftChopstick,  Chopstick rightChopstick, String name, boolean taoQi) {
        this.leftChopstick = leftChopstick;
        this.rightChopstick = rightChopstick;
        this.name = name;
        this.taoQi = taoQi;
    }

    @Override
    public void run() {
        eat();
        think();
    }

    public void eat(){
        System.out.println(name + "饥饿");
        if (taoQi) {
            System.out.println(name + "很淘气");
            rightChopstick.requestUse(name);            
            leftChopstick.requestUse(name);
        } else {
            leftChopstick.requestUse(name);
            rightChopstick.requestUse(name);
        }

        try {
            Thread.sleep((long) (Math.random() * 1000));
        } catch (InterruptedException e) {
            e.printStackTrace();;
        }
        leftChopstick.giveupUse(name);
        rightChopstick.giveupUse(name);
    }

    public void think() {
        try {
            Thread.sleep((long) (Math.random() * 1000));
        } catch (InterruptedException e) {
            e.printStackTrace();;
        }
    }
}
/**
 * 可能死锁的情况
 */
public class DeadlockTest {

    public static void main(String[] args) {
        List<Chopstick> chopsticks = new ArrayList<>();
        for (int i = 0; i < 3; i++) {
            chopsticks.add(new Chopstick("筷子" + (i + 1)));
        }
        int taoQi = (int) (Math.random() * 3 + 1);
        Philosopher philosopher1 = new Philosopher(chopsticks.get(0), chopsticks.get(1), "哲学家1", 1 == taoQi);
        Philosopher philosopher2 = new Philosopher(chopsticks.get(1), chopsticks.get(2), "哲学家2", 2 == taoQi);
        Philosopher philosopher3 = new Philosopher(chopsticks.get(2), chopsticks.get(0), "哲学家3", 3 == taoQi);
        new Thread(philosopher2).start();
        new Thread(philosopher1).start();
        new Thread(philosopher3).start();
    }
}

六、修改后的结果

哲学家2饥饿
哲学家2很淘气
哲学家3饥饿
哲学家1饥饿
哲学家3申请筷子3,成功!
哲学家2申请筷子3,被阻塞
哲学家1申请筷子1,成功!
哲学家1申请筷子2,成功!
哲学家3申请筷子1,被阻塞
哲学家1吃完,放弃筷子1,并唤醒所有等待的哲学家
哲学家1吃完,放弃筷子2,并唤醒所有等待的哲学家
哲学家3申请筷子1,成功!
哲学家3吃完,放弃筷子3,并唤醒所有等待的哲学家
哲学家3吃完,放弃筷子1,并唤醒所有等待的哲学家
哲学家2申请筷子3,成功!
哲学家2申请筷子2,成功!
哲学家2吃完,放弃筷子2,并唤醒所有等待的哲学家
哲学家2吃完,放弃筷子3,并唤醒所有等待的哲学家

猜你喜欢

转载自blog.csdn.net/qq_38977097/article/details/80945940