Java多线程(4)---死锁和Synchronized加锁流程

news/2024/5/20 12:48:11 标签: java, 开发语言, 死锁, java-ee

目录

前言

一.synchronized

1.1概念 

1.2Synchronized是什么锁?

1.3Synchronized加锁工作过程

1.4其他优化操作

二.死锁

2.1什么是死锁

2.2死锁的几个经典场景

2.3死锁产生的条件

2.4如何解决死锁


🎁个人主页:tq02的博客_CSDN博客-C语言,Java,Java数据结构领域博主
🎥 本文由 tq02 原创,首发于 CSDN🙉
🎄 本章讲解内容:线程的策略锁、CAS和JUC

🎥多线程学习专栏:多线程学习专栏

🎥其他学习专栏:  C语言         JavaSE       MySQL基础 

前言

        在多线程的讲解当中,我们可以知道synchronized是加锁操作,让两个线程发生互斥效果,在代码中使用synchronized关键字来实现锁的获取和释放。如果是刚刚接触多线程的人,我希望你可以从第一章多线程开始学习:http://t.csdn.cn/0vEhY

一.synchronized

1.1概念 

        Synchronized是Java中内置的锁机制,用于实现线程同步。它可以通过在代码中使用synchronized关键字来实现锁的获取和释放。Synchronized关键字可以用在方法上或者代码块中。当一个线程执行到synchronized修饰的代码块时,它会尝试获取锁,如果锁没有被其他线程占用,则获取成功,执行代码块中的内容。如果锁已经被其他线程占用,则该线程会进入等待状态,直到获取到锁才能继续执行。

1.2Synchronized是什么锁?

  1. 开始时是乐观锁, 如果锁冲突频繁, 就转换为悲观锁.
  2. 开始是轻量级锁实现, 如果锁被持有的时间较长, 就转换成重量级锁.
  3. 实现轻量级锁的时候大概率用到的自旋锁策略
  4. 是一种不公平锁
  5. 是一种可重入锁
  6. 不是读写锁
     

注:需要使用公平锁,建议使用ReentrantLock来实现。ReentrantLock提供了公平锁和非公平锁两种模式,通过构造函数的参数来指定锁的模式。

1.3Synchronized加锁工作过程

        对于锁资源只有一个或者两个线程交替竞争的,仍然需要使用系统调用,无疑对CPU资源是极大的消耗。因此,在jdk1.6针对Synchronized加锁进行了优化。按对锁的竞争程度划分成:无锁,偏向锁,轻量级锁,重量级锁。简单而言就是从无锁-->重量级锁。 

无锁

当你添加了锁时,如果编译器认为不需要加锁,会自动删除,因此便是无锁

偏向锁

偏向锁不是真的 "加锁", 只是给对象头中做一个 "偏向锁的标记", 记录这个锁属于哪个线程.
如果后续没有其他线程来竞争该锁, 那么就不用进行其他同步操作了(避免了加锁解锁的开销)
如果后续有其他线程来竞争该锁(刚才已经在锁对象中记录了当前锁属于哪个线程了, 很容易识别当前申请锁的线程是不是之前记录的线程), 那就取消原来的偏向锁状态, 进入一般的轻量级锁状态.
注:相当于做个标记,相当于 "延迟加锁" . 能不加锁就不加锁, 尽量避免不必要的加锁开销.

轻量级锁
随着其他线程进入竞争, 偏向锁状态被消除, 进入轻量级锁状态(自适应的自旋锁).
此处的轻量级锁就是通过 CAS 来实现

  1. 通过 CAS 检查并更新一块内存 (比如 null => 该线程引用)
  2. 如果更新成功, 则认为加锁成功
  3. 如果更新失败, 则认为锁被占用, 继续自旋式的等待(并不放弃 CPU).

注:此处的自旋锁不会一种持续进行,而是达到一定的时间/重试次数, 就不再自旋了.

重量级锁
        如果锁竞争进一步激烈, 自旋不能快速获取到锁状态, 就会膨胀为重量级锁

此处的重量级锁就是指用到内核提供的 mutex .
具体流程:

  • 执行加锁操作, 先进入内核态.
  • 在内核态判定当前锁是否已经被占用
  • 如果该锁没有占用, 则加锁成功, 并切换回用户态.
  • 如果该锁被占用, 则加锁失败. 此时线程进入锁的等待队列, 挂起. 等待被操作系统唤醒.
  • 经历了一系列的沧海桑田, 这个锁被其他线程释放了, 操作系统也想起了这个挂起的线程, 于是唤醒
  • 这个线程, 尝试重新获取锁

1.4其他优化操作

        我们额外补充2个编译器对锁的优化操作。锁消除和锁粗化

锁消除

        代码中, 用到了 synchronized, 但其实没有在多线程环境下. (例如 StringBuffer)

java">StringBuffer tq02 = new StringBuffer();
tq02.append("a");
tq02.append("b");
tq02.append("c");
tq02.append("d");

每个 append 的调用都会涉及加锁和解锁. 但如果只是在单线程中执行这个代码, 那么这些加
锁解锁操作是没有必要的, 白白浪费了一些资源开销.因此将锁给优化了。

锁粗化

锁的粗化是根据锁的粒度:粗和细

 实际开发过程中, 使用细粒度锁, 是期望释放锁的时候其他线程能使用锁.但可能并没有其他线程来抢占这个锁. 这种情况 JVM 就会自动把锁粗化, 避免频繁申请释放锁.
 

二.死锁

2.1什么是死锁

        死锁是指在多进程系统中,每个进程都在等待某个资源,而该资源又被其他进程占用,导致所有进程都无法继续执行的状态。

例如:A、B、C、D和E去上厕所,A进入厕所并且锁门,B.C.D等待,可是A刚刚进入厕所,因为特殊的原因,凭空转移到了外面,A就得重新排队,可是门还是锁着的啊,因此导致了死锁

2.2死锁的几个经典场景

经典场景有:

  1. 一个线程,一把锁
  2. 两个线程,两把锁
  3. 多个线程,多把锁

1.一个线程,一把锁

        一个线程连续被同一个加锁两次,如果是不可重入锁,那么会是死锁

解析:我去上厕所,我把厕所门锁住,再通过厕所的窗户出去,然后再来上厕所,发现厕所锁住了,就耐心等待,却没想过这是自己锁的。

代码实现:

java">public class Counter {
        
   void increase() {       
     synchronize(this){
       increase()   //可以理解为翻窗逃走,第二次加锁时,是锁了的
    }
}

public static void main(String[] args) throws InterruptedException {
    final Counter counter = new Counter();
        Thread t1 = new Thread(() -> {
                counter.increase();
        });
            t1.start();
    }
}

2.两个线程,两把锁

        线程1先获取锁A,再尝试获取锁B,同时,线程2先获取锁B,再尝试获取锁A,此时两个线程就会互相僵住,谁都获取不到对方持有的锁。

解析:我在汽车里,车钥匙在我妻子手上,我出不来,我妻子在房间里,房间钥匙在我手上,我妻子也出不来,导致双方被锁,导致死锁

代码示例:

java">public class Test {
    public static void main(String[] args) {
        //2个锁对象
        Object lockerA = new Object();
        Object lockerB = new Object();
 
        Thread t1 = new Thread(() -> {
            System.out.println("t1尝试获取锁A");
            synchronized (lockerA){
                System.out.println("t1获取到锁A");
                try {
                    Thread.sleep(100);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                System.out.println("t1尝试获取锁B");
                synchronized (lockerB){
                    System.out.println("t1获取到锁B");
                }
            }
        });
        Thread t2 = new Thread(() -> {
            System.out.println("t2尝试获取锁B");
            synchronized (lockerB){
                System.out.println("t2获取到锁B");
                try {
                    Thread.sleep(100);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                System.out.println("t2尝试获取锁A");
                synchronized (lockerA){
                    System.out.println("t2获取到锁A");
                }
            }
        });
 
        t1.start();
        t2.start();
    }
}

3.多个线程,多把锁

        很明显啊,两把锁,两个线程也有问题,更何况是多把锁啊,在这方面最经典的是"哲学家就餐问题"。

如图:火柴人是哲学家、红线是筷子,每一个哲学家的左右都有一根筷子。规定,当有一根哲学家饿了,会先拿起左边的筷子,然后再拿右边的筷子,吃完了就放下筷子。

造成死锁问题:每一个哲学家都饿了,然后都拿起了左边的筷子,可是当拿右边的筷子时,发现有其他人在使用,所以导致阻塞,然后一直等待别人吃饱放下筷子,可是每个人都在等待。

2.3死锁产生的条件

死锁产生需要四个条件:

  1. 互斥使用,即当资源被一个线程使用(占有)时,别的线程不能使用
  2. 不可抢占,资源请求者不能强制从资源占有者手中夺取资源,资源只能由资源占有者主动释放。
  3. 请求和保持,即当资源请求者在请求其他的资源的同时保持对原有资源的占有。
  4. 循环等待,即存在一个等待队列:P1占有P2的资源,P2占有P3的资源,P3占有P1的资源。这样就形成了一个等待环路。

当上述四个条件都成立的时候,便形成死锁。当然,死锁的情况下如果打破上述任何一个条件,便可让死锁消失。
 

2.4如何解决死锁

        想没有死锁,那么我们可以从死锁产生的条件入手,只有破坏其他一条就可以了。 互斥使用和不可抢占是锁的基本特性,因此无法干预,但是请求和保持,也不可能改变,因为这是代码执行逻辑。因此只有循环等待,我们可以打破

        为了解决死锁问题,可以采取预防、避免、检测和解除四种方法。

预防:通过设置某些限制条件,以防止死锁的发生。

避免:系统在分配资源时根据资源的使用情况提前作出预测,从而避免死锁的发生。

检测:允许系统在运行过程中产生死锁,但系统中有相应的管理模块可以及时检测出已经产生的死锁,并精确地确定与死锁有关的进程和资源,然后采取适当措施清除系统中已经产生的死锁

解除:当发现有进程死锁后,立即解脱它从死锁状态中出来。常用的方法包括剥夺资源和撤销进程。剥夺资源是从其他进程中剥夺足够数量的资源给死锁进程,以解除死锁状态。撤销进程可以直接撤销死锁进程或撤销代价最小的进程,直至有足够的资源可用,从而消除死锁状态。


                                                                                                        ---------------------懒惰的tq02


http://www.niftyadmin.cn/n/4934512.html

相关文章

AI项目一:mediapipe测试

若该文为原创文章,转载请注明原文出处。 一. 引言 MediaPipe 是一款由 Google Research 开发并开源的多媒体机器学习模型应用框架。在谷歌,一系列重要产品,如 YouTube、Google Lens、ARCore、Google Home 以及 Nest,都已深度整合了…

【博客694】k8s kubelet 状态更新机制

k8s kubelet 状态更新机制 场景: 当 Kubernetes 中 Node 节点出现状态异常的情况下,节点上的 Pod 会被重新调度到其他节点上去,但是有的时候我们会发现节点 Down 掉以后,Pod 并不会立即触发重新调度,这实际上就是和 K…

使用阿里云服务器部署和使用GitLab

本文阿里云百科分享使用阿里云服务器部署和使用GitLab,GitLab是Ruby开发的自托管的Git项目仓库,可通过Web界面访问公开的或者私人的项目。本教程介绍如何部署和使用GitLab。 目录 准备工作 部署GitLab环境 使用GitLab 登录GitLab 生成密钥对文件并…

AI 绘画Stable Diffusion 研究(七) 一文读懂 Stable Diffusion 工作原理

大家好,我是风雨无阻。 本文适合人群: 想要了解AI绘图基本原理的朋友。 对Stable Diffusion AI绘图感兴趣的朋友。 本期内容: Stable Diffusion 能做什么 什么是扩散模型 扩散模型实现原理 Stable Diffusion 潜扩散模型 Stable Diffu…

读书笔记 |【项目思维与管理】➾ 顺势而动

读书笔记 |【项目思维与管理】➾ 顺势而动 一、企业步入“终结者时代”二、过去成功的经验也许是最可怕的三、做好非重复性的事四、适应客户是出发点五、向知识型企业转变六、速度是决胜条件 💖The Begin💖点点关注,收藏不迷路💖 …

工业机器视觉系统开发流程简介

需求分析和系统设计:与用户合作,明确系统的功能和性能需求,并设计系统的整体架构。 软、硬件选型:根据需求分析结果,选择适合的软、硬件设备,包括光学传感器、相机、光源、图像采集设备、处理器等。 软件…

Linux:Shell编程之免交互

目录 绪论 1、here Document免交互 1.1 格式 1.2 cat结合免交互实现重定向输出到指定文件 1.3 变量替换 2、Expect免交互 2.1 三种写法 3、免交互实现普通用户切换root 3.1 send_user 4、接收参数 5、嵌入执行模式 6、ssh远程登录 绪论 免交互:不需要人…

力求超越ChatGPT,谷歌加入人工智能新项目

受到新贵OpenAI的威胁,谷歌承诺快速跟踪新的人工智能项目。 ChatGPT吓坏了谷歌。周五,纽约时报据报告的谷歌创始人拉里佩奇和谢尔盖布林与公司高管就OpenAI召开了几次紧急会议新聊天机器人谷歌认为这可能威胁到其价值1490亿美元的搜索业务。 由OpenAI创…