• 操作系统-死锁,锁


     一:死锁

    1.定义

    所谓死锁,是指两个或者以上线程并行执行的时候,争夺资源而互相等待造成的。当进程处于这种僵持状态时,若无外力作用,它们都将无法再向前推进。

    2.死锁产生的四个必要条件?

    死锁只有同时满足以下四个条件才会发生:

    • 互斥:多个线程不能同时使用同一个资源。
    • 请求和保持:当线程因请求资源而阻塞时,对已获得的资源保持不放。
    • 不可剥夺:线程已获得的资源在未使用完之前,不能剥夺,只能在使用完时由自己释放。
    • 环路等待:在死锁发生的时候,两个线程获取资源的顺序构成了环形链。

    3.解决死锁的基本方法

    避免死锁问题,就是要破坏其中一个条件即可,最常用的方法就是使用资源有序分配法来破坏环路等待条件。

    • 资源一次性分配,一次性分配所有资源,这样就不会再有请求了;(破坏请求条件)
    • 只要有一个资源得不到分配,也不给这个进程分配其他的资源;(破坏请保持条件)
    • 可剥夺资源,即当某进程获得了部分资源,但得不到其它资源,则释放已占有的资源(破坏不可剥夺条件)
    • 资源有序分配法,系统给每类资源赋予一个编号,每一个进程按编号递增的顺序请求资源,释放则相反(破坏环路等待条件)

    二:常见锁策略

    多线程访问共享资源的时候,避免不了资源竞争而导致数据错乱的问题,所以我们通常为了解决这一问题,都会在访问共享资源之前加锁。加锁的目的就是保证共享资源在任意时间里,只有一个线程访问,这样就可以避免多线程导致共享数据错乱的问题。

     1) 你是怎么理解乐观锁和悲观锁的,具体怎么实现呢?

    悲观锁认为多个线程访问同一个共享变量冲突的概率较大, 会在每次访问共享变量之前都去真正加 锁.。乐观锁认为多个线程访问同一个共享变量冲突的概率不大. 并不会真的加锁, 而是直接尝试访问数据. 在访问的同时识别当前的数据是否出现访问冲突。

    悲观锁的实现就是先加锁(比如借助操作系统提供的 mutex), 获取到锁再操作数据. 获取不到锁就 等待. 乐观锁的实现可以引入一个版本号. 借助版本号识别出当前的数据访问是否冲突. 

    Synchronized 初始使用乐观锁策略. 当发现锁竞争比较频繁的时候, 就会自动切换成悲观锁策略. 就好比同学 C 开始认为 "老师比较闲的", 问问题都会直接去找老师. 但是直接来找两次老师之后, 发现老师都挺忙的, 于是下次再来问问题, 就先发个消息问问老师忙不 忙, 再决定是否来问问题.

    2) 介绍下读写锁?

    读写锁就是把读操作和写操作分别进行加锁. 读锁和读锁之间不互斥. 写锁和写锁之间互斥. 写锁和读锁之间互斥. 读写锁最主要用在 "频繁读, 不频繁写" 的场景中.Synchronized 不是读写锁.

    3) 什么是自旋锁,为什么要使用自旋锁策略呢,缺点是什么?

    相比于挂起等待锁, 优点: 没有放弃 CPU 资源, 一旦锁被释放就能第一时间获取到锁, 更高效. 在锁持有时间比较短的场 景下非常有用. 缺点: 如果锁的持有时间较长, 就会浪费 CPU 资源.

    • 互斥锁加锁失败后,线程会释放 CPU ,给其他线程;
    • 自旋锁加锁失败后,线程会忙等待,直到它拿到锁;如果获取锁失败, 立即再尝试获取锁, 无限循环, 直到获取到锁为止. 第一次获取锁失败, 第二次的尝试会 在极短的时间内到来. 一旦锁被其他线程释放, 就能第一时间获取到锁.

    如果你能确定被锁住的代码执行时间很短,就不应该用互斥锁,而应该选用自旋锁,否则使用互斥锁。

    互斥锁加锁失败时,会从用户态陷入到内核态,让内核帮我们切换线程,虽然简化了使用锁的难度,但是存在一定的性能开销成本。自旋锁是通过 CPU 提供的 CAS 函数(Compare And Swap),在「用户态」完成加锁和解锁操作,不会主动产生线程上下文切换,所以相比互斥锁来说,会快一些,开销也小一些。

    4) synchronized 是可重入锁么?

    是可重入锁. 可重入锁指的就是连续两次加锁不会导致死锁,允许同一个线程多次获取同一把锁。 实现的方式是在锁中记录该锁持有的线程身份, 以及一个计数器(记录加锁次数). 如果发现当前加锁 的线程就是持有锁的线程, 则直接计数自增.

    Java里只要以Reentrant开头命名的锁都是可重入锁,而且JDK提供的所有现成的Lock实现类,包括 synchronized关键字锁都是可重入的。 而 Linux 系统提供的 mutex 是不可重入锁.

    5) 讲解下你自己理解的 CAS 机制

    全称 Compare and swap, 即 "比较并交换". 相当于通过一个原子的操作, 同时完成 "读取内存, 比 较是否相等, 修改内存" 这三个步骤. 本质上需要 CPU 指令的支撑.

    2)ABA问题怎么解决?

    ABA问题(好比, 我们买一个手机, 无法判定这个手机是刚出厂的新手机, 还是别人用旧了, 又翻新过的手 机.)给要修改的数据引入版本号. 在 CAS 比较数据当前值和旧值的同时, 也要比较版本号是否符合预期. 如果发现当前版本号和之前读到的版本号一致, 就真正执行修改操作, 并让版本号自增; 如果发现当 前版本号比之前读到的版本号大, 就认为操作失败

    三:synchronized

    1.什么是synchronized关键字?

    在多线程的环境下,多个线程同时访问共享资源会出现一些问题,而synchronized关键字则是用来保证线程同步的。

    synchronized关键字的使用方式主要有三种:

    修饰普通同步方法、修饰静态同步方法、修饰同步方法块。

    2.synchronized关键字三大特性是什么?

    面试时经常拿 synchronized关键字和 volatile关键字的特性进行对比, synchronized关键字可以保证并发编程的三大特性:原子性、可见性、有序性,而 volatile关键字只能保证可见性和有序性,不能保证原子性,也称为是轻量级的 synchronized
    • 原子性:一个或多个操作要么全部执行成功,要么全部执行失败。synchronized关键字可以保证只有一个线程拿到锁,访问共享资源。
    • 可见性:当一个线程对共享变量进行修改后,其他线程可以立刻看到。执行synchronized时,会对应执行 lockunlock原子操作,保证可见性。
    • 有序性:程序的执行顺序会按照代码的先后顺序执行。

    3.synchronized关键字可以实现什么类型的锁?

    • 悲观锁:synchronized关键字实现的是悲观锁,每次访问共享资源时都会上锁。
    • 非公平锁:synchronized关键字实现的是非公平锁,即线程获取锁的顺序并不一定是按照线程阻塞的顺序。
    • 可重入锁:synchronized关键字实现的是可重入锁,即已经获取锁的线程可以再次获取锁。
    • 独占锁或者排他锁:synchronized关键字实现的是独占锁,即该锁只能被一个线程所持有,其他线程均被阻塞。

    4.synchronized和volatile的区别?

    • volatile主要是保证内存的可见性,即变量在寄存器中的内存是不确定的,需要从主存中读取。synchronized主要是解决多个线程访问资源的同步性。
    • volatile作用于变量,synchronized作用于代码块或者方法。
    • volatile仅可以保证数据的可见性,不能保证数据的原子性。synchronized可以保证数据的可见性和原子性。
    • volatile不会造成线程的阻塞,synchronized会造成线程的阻塞。
  • 相关阅读:
    python开发-Flask与Vue基础
    Java多线程编程核心技术手册 绝了
    uniapp app或微信小程序项目使用gite仓库中的图片
    Node.js【Express 中间件(中间件的概念、Express 中间件的初体验、中间件的分类、自定义中间件)】
    流量渠道分析
    php 使用ossClient->listObjects,报错502
    Linux08——面试题篇
    LLM研究之-NVIDIA的CUDA
    国科云DDI网络服务 | 政府和中大型企业IP管理的全新解决方案
    12-k8s-HPA自动扩缩容
  • 原文地址:https://blog.csdn.net/weixin_45780538/article/details/126347955