详解多线程死锁&lock锁&生产者消费者问题

本文探讨了死锁的概念、Java中的ReentrantLock实例及其在生产者消费者问题中的应用,通过信号灯法解决并发协作中的死锁问题,并比较了synchronized与Lock的区别。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

死锁

  • 定义 多个线程各自占有一些公共资源,并且互相等待其它线程占有的的资源才能运行,而导致两个或者多个线程都在等待对方释放资源,都停止执行的情况,某一个同步块同时拥有“两个以上对象的锁”,就可能发生死锁问题。

图示

在这里插入图片描述

将上述图示代码化

//模拟口红
class Lipstick{
}
//模拟镜子
class Mirror{
}
//占有资源
class Makeup extends Thread{
    //需求资只有一份所以置为静态
    static Lipstick lipstick=new Lipstick();
    static  Mirror mirror=new Mirror();
    int choice;
    String girlName;
    Makeup(int choice,String girlName){
        this.choice=choice;
        this.girlName=girlName;
    }
    public void run(){
        try {
            makeup();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        //化妆
    }
    //互相持有对象的锁,也就是对方的资源
    private void makeup() throws InterruptedException {
        if (choice==0){
            synchronized (lipstick){
                System.out.println(this.girlName+"获得口红的锁");
                Thread.sleep(1000);
                //一秒钟后想获得镜子的锁
                synchronized (mirror){
                    System.out.println(this.girlName+"获得镜子的锁");
                }
            }
        }
        else{
            synchronized (mirror){
                System.out.println(this.girlName+"获得镜子的锁");
                Thread.sleep(2000);
                //一秒钟后想获得镜子的锁
                synchronized (lipstick){
                    System.out.println(this.girlName+"获得口红的锁");
                }
            }
        }

我们让两个线程同时跑

 Makeup her1=new Makeup(0,"灰姑娘");
 Makeup her2=new Makeup(1,"白雪公主");

在这里插入图片描述

有问题的商量解决 ,用完资源之后将占有的锁让出不就实现了解死锁了吗?

在这里插入图片描述

  • 总结死锁:
  • 互斥条件一个资源每次只能被一个进程使用
  • 请求与保持条件:一个进程因请求资源而阻塞时,对已经获得的资源保持不
  • 不剥夺条件:进程已经获得的资源,在未使用之前,不可以进行强行剥夺
  • 循环等待条件:若干进程之间形成一种头尾相接的循环等待资源的关系。

解决办法:想办法破坏其中一个

lock锁

  • 关于lock锁
  • 更加强大的线程同步机制–显示定义同步锁来对对象实现同步
  • locks.lock接口是控制多个线程对共享资源进行访问的工具,所提供了对对象资源的独占访问,每次只能有一个线程对lock对象加锁,线程访问共享资源之前应先获得Lock对象
  • ReentrantLock 类实现了Lock ,他拥有了与synchronized 相同的并发性和内存语义,在实现线程安全的控制中,比较常用的是ReentrantLock,可以显示加锁,释放锁.

让我们再来回忆一下买票的问题

在这里插入图片描述
在上一篇中我们尝试用了synchronized 同步方法来解决线程不安全的情况这次我们用一个Lock锁再来解决一次

定义一把锁

private  final ReentrantLock lock=new ReentrantLock();

在代码块之间加锁 ->开锁

 lock.lock();
 //代码区
 lock.unlock();

在这里插入图片描述

双锁对比 : synchronized &&lock

  • lock是显示锁需要进行手动开启关闭,synchronized是隐式锁 除了作用域自动释放
  • lock锁只有代码块,synchronized有代码块锁和方法锁
  • 使用lock锁 JVM将花费较少的时间来调度线程,性能更好,并且具有更好的扩展性 提供更多子类
  • 优先使用顺序:lock> 同步块> 同步方法

线程协作:生产者消费者模式

线程通信
应用场景:生产者和消费者问题
  • 假设一个中只能存放一件产品,生产者将生产出来的产品放入仓库,消费者将仓库中的产品取走消费.
  • 如果仓库没有产品,则生产者将产品放入仓库,否则停止生产并等待,直到仓库中的产品被消费者取走为止
  • 如果仓库中放有产品,则消费者可以将产品取走消费,否则停止消费等待,直到仓库中再次放入产品为止

在这里插入图片描述

线程通信分析

生产者和消费者之间共享同一个资源,并且生产者和消费者之间互为依赖,互为条件。
  • 对于生产者,没有生产产品之前要通知消费者等待,而生产了产品之后,又要马上通知消费者消费。
  • 对于消费者,在消费者之后,要通知生产者已经结束消费,需要生产新的产品以供消费
  • 在生产者消费者问题中,仅有synchronized是不够的
  • synchronized可阻止并发更新同一个共享资源,实现了同步。
  • synchronized不能用来实现不同线程之间的消息传递
方法名作用
wait()表示线程一直等待,直到其它线程通知,与sleep不同会释放锁
wait(long time out)指定等待的毫秒数
notify()唤醒一个正在处于等待状态的线程
notifyall()唤醒同一对象上所有调用wait()方法的线程,优先级别高的优先调度

均是Object类的方法 ,都只能在同步方法或者同步代码块中使用

解决方式一:

并发协作模型 生产者/消费者模式—>管程法

  • 生产者:负责生产数据的模块
  • 消费者:负责处理数据的模块
  • 缓冲区:消费者不能直接使用消费者的数据,他们之间得有个缓冲区

上图

解决方式二:

并发协作模型:生产者/消费者模式—>信号灯法

在这里插入图片描述

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值