【多线程学习九】通过ReentrantLock解决哲学家就餐问题

本文探讨了五个哲学家就餐问题,通过Java ReentrantLock实现避免死锁的解决方案。介绍可重入锁、可中断、超时和条件变量在解决哲学家困境中的应用,展示了如何使用tryLock方法和条件变量来确保并发安全。

哲学家就餐问题

在这里插入图片描述

五个哲学家,只做两件事,思考和吃饭。

吃饭要用到两根筷子,每个哲学家左手边和右手边各有一根筷子,而桌上只有五根,当筷子被旁边的人拿着,自己就只能进入等待

筷子类

class Chopstick {
    String name;
    
    public Chopstick(String name) {
        this.name = name;
    }
    
    @Override
    public String toString() {
        return "筷子{" + name + '}';
    }
}

哲学家类

class Philosopher extends Thread {
    Chopstick left;
    Chopstick right;
    
    public Philosopher(String name, Chopstick left, Chopstick right) {
        super(name);
        this.left = left;
        this.right = right;
    }
    
    private void eat() {
        log.debug("eating...");
        Sleeper.sleep(1);
    }
    
    @Override
    public void run() {
        while (true) {
            // 获得左手筷子
            synchronized (left) {
                // 获得右手筷子
                synchronized (right) {
                    // 吃饭
                    eat();
                }
                // 放下右手筷子
            }
            // 放下左手筷子
        }
    }
    
}

就餐(执行)

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");

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();

执行后,用jconsole检测死锁发现五位哲学家都进入了死锁状态

ReentrantLock

可重入锁,顾名思义,支持可重入

  • 可中断
  • 可设置超时时间
  • 可设置为公平锁(先进先出,处理饥饿,但是一般不使用)
  • 支持多个条件变量(Condition)

基本语法
在这里插入图片描述

  • 可重入指线程1首次获得了锁A,因为线程1是锁A的拥有者,所有有权利再次获取这把锁
  • 不可重入锁,第二次获得锁时,自己也会被锁挡住
@Slf4j
public class Reentrant {
    static ReentrantLock lock = new ReentrantLock();

    public static void main(String[] args) {
        method1();
    }

    public static void method1(){
//            获取锁
            lock.lock();
            try {
//              临界区
                log.info("execute method1");
                method2();
            }finally {
//                释放锁
                lock.unlock();
            }
    }

    public static void method2(){
//            获取锁
        lock.lock();
        try {
//              临界区
            log.info("execute method2");
        }finally {
//                释放锁
            lock.unlock();
        }
    }
}

在这里插入图片描述

可打断

lock.lockInterruptibly(),可以防止线程一直等待锁

@Slf4j
public class Reentrant {
    static ReentrantLock lock = new ReentrantLock();

    public static void main(String[] args) {
       Thread t1 = new Thread(()->{
          log.info("start...");
          try {
//              判断锁是否被打断,打断了抛出异常
//              没有打断则get lock...执行完后释放锁
              lock.lockInterruptibly();
          }catch (InterruptedException e){
              e.printStackTrace();
              log.info("waiting is interrupt...");
              return;
          }

          try {
              log.info("get lock....");
          }finally {
              // 释放锁
              lock.unlock();
          }
       },"t1");
       lock.lock();
       log.info("get lock ....");
       t1.start();
       try {
           Sleepy.sleep(1000);
           t1.interrupt();
           log.info("interrupt execute");
       }finally {
           lock.unlock();
       }
    }
}

主线程获取锁后,执行t1线程,此时t1线程被打断,抛出异常后跳出循环

在这里插入图片描述

如果是不可中断模式,即使使用了interrupt也不会让等待中断

@Slf4j
public class Reentrant {
    static ReentrantLock lock = new ReentrantLock();

    public static void main(String[] args) {
       Thread t1 = new Thread(()->{
          log.info("start...");
          lock.lock();
          try {
              log.info("get lock....");
          }finally {
              // 释放锁
              lock.unlock();
          }
       },"t1");
       lock.lock();
       log.info("get lock ....");
       t1.start();
       try {
           Sleepy.sleep(1000);
           t1.interrupt();
           log.info("interrupt execute");
       }finally {
           lock.unlock();
       }
    }
}

t1不会因为interrupt而获取不到锁

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-lz4LaEAE-1655570755988)(D:\Note\面试学习笔记.assets\image-20220619001322326.png)]

锁超时
立刻失败
	static ReentrantLock lock = new ReentrantLock();
    public static void main(String[] args) {
        Thread t1 = new Thread(()->{
            log.info("start.....");
//            获取锁失败时立刻失败
            if(!lock.tryLock()){
                log.info("fail to get the lock.....");
                return;
            }
            try {
                log.info("get lock...");
            }finally {
                lock.unlock();
            }
        },"t1");

        lock.lock();
        t1.start();
        try {
            Sleepy.sleep(1000);
        }finally {
            lock.unlock();
        }
    }

在这里插入图片描述

超时失败

t1在1s后,因为主线程释放了锁,所以获取锁成功

 public static void main(String[] args) {
        Thread t1 = new Thread(()->{
            log.info("start.....");
//            获取锁失败时等待1秒
            try {
                if(!lock.tryLock(1, TimeUnit.SECONDS)){
                    log.info("fail to get the lock.....");
                    return;
                }
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            try {
                log.info("get lock...");
            }finally {
                lock.unlock();
            }
        },"t1");

        lock.lock();
        t1.start();
        try {
            Sleepy.sleep(1000);
        }finally {
            lock.unlock();
        }
    }

在这里插入图片描述

使用tryLock解决哲学家问题

class Chopstick extends ReentrantLock {
    String name;
    
    public Chopstick(String name) {
        this.name = name;
    }
    
    @Override
    public String toString() {
        return "筷子{" + name + '}';
    }
}
class Philosopher extends Thread {
    
    Chopstick left;
    Chopstick right;
    
    public Philosopher(String name, Chopstick left, Chopstick right) {
        super(name);
        this.left = left;
        this.right = right;
    }
    
    @Override
    public void run() {
        while (true) {
            // 尝试获得左手筷子
            if (left.tryLock()) {
                try {
                    // 尝试获得右手筷子
                    if (right.tryLock()) {
                        try {
                            eat();
                        } finally {
                            right.unlock();
                        }
                    }
                } finally {
                    left.unlock();
                }
            }
        }
    }
    
    private void eat() {
        log.debug("eating...");
        Sleeper.sleep(1);
    }
    
}
条件变量

Synchronized也有条件变量,在monitor中的waitSet,当条件不满足时进入waitSet等待

ReentrantLock的条件变量可以有多个

使用要点

  • await前需要获取锁
  • await执行后会释放锁,进入条件变量对象(conditionObject)等待
  • await的线程==被唤醒(打断/超时)==去重新竞争lock锁
  • 竞争lock锁成功后,从await后继续执行

通过公车和汽车两个条件,创建对应的条件变量

​ --》当还没有车启动时,进入等待状态,此时获得了锁,在此期间一直等待车子发动

​ --》当车发动后,条件变量收到通知,开始上车操作

@Slf4j
public class MutilCondition {
    static ReentrantLock lock = new ReentrantLock();
    static Condition waitCar = lock.newCondition();
    static Condition waitBus = lock.newCondition();
    static volatile boolean hasCar = false;
    static volatile boolean hasBus = false;

    public static void main(String[] args) {
        new Thread(()->{
            try {
                lock.lock();
                while(!hasCar){
                    try {
                        waitCar.await();
                    }catch (InterruptedException e){
                        e.printStackTrace();
                    }
                }
                log.info("car coming and get...");
            }finally {
//                等到汽车后释放锁
                lock.unlock();
            }
        }).start();


        new Thread(()->{
            try {
                lock.lock();
                while (!hasBus){
                    try {
                        waitBus.await();
                    }catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
                log.info("bus coming and get...");
            }finally {
                lock.unlock();
            }
        }).start();

        Sleepy.sleep(1000);
        startCar();
        Sleepy.sleep(1000);
        startBus();

    }


    private static void startCar(){
        lock.lock();
        try {
            log.info("car is starting...");
            hasCar = true;
//            等汽车的条件变量收到通知
            waitCar.signal();
        }finally {
            lock.unlock();
        }
    }

    private static void startBus(){
        lock.lock();
        try {
            log.info("bus is starting...");
            hasBus = true;
//            等公交的条件变量收到通知
            waitBus.signal();
        }finally {
            lock.unlock();
        }
    }
}

在这里插入图片描述

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值