精华内容
下载资源
问答
  • 数据库和操作系统一样,是一个多用户使用的共享资源...在实际应用中经常会遇到的与锁相关的异常情况,当两个事务需要一组有冲突的锁,而不能将事务继续下去的话,就会出现死锁,严 重影响应用的正常执行。 在数据库...

    欢迎大家关注我的公众号【老周聊架构】,Java后端主流技术栈的原理、源码分析、架构以及各种互联网高并发、高性能、高可用的解决方案。

    数据库和操作系统一样,是一个多用户使用的共享资源。当多个用户并发地存取数据 时,在数据库中就会产生多个事务同时存取同一数据的情况。若对并发操作不加控制就可能会读取和存储不正确的数据,破坏数据库的一致性。加锁是实现数据库并 发控制的一个非常重要的技术。在实际应用中经常会遇到的与锁相关的异常情况,当两个事务需要一组有冲突的锁,而不能将事务继续下去的话,就会出现死锁,严 重影响应用的正常执行。

    在数据库中有两种基本的锁类型:排它锁(Exclusive Locks,即X锁)和共享锁(Share Locks,即S锁)。当数据对象被加上排它锁时,其他的事务不能对它读取和修改。加了共享锁的数据对象可以被其他事务读取,但不能修改。数据库利用这两 种基本的锁类型来对数据库的事务进行并发控制。

    一、事务之间对资源访问顺序的交替

    1、出现原因:

    一个用户A 访问表A(锁住了表A),然后又访问表B;另一个用户B 访问表B(锁住了表B),然后企图访问表A;这时用户A由于用户B已经锁住表B,它必须等待用户B释放表B才能继续,同样用户B要等用户A释放表A才能继续,这就死锁就产生了。

    2、解决方法:

    这种死锁比较常见,是由于程序的BUG产生的,除了调整的程序的逻辑没有其它的办法。仔细分析程序的逻辑,对于数据库的多表操作时,尽量按照相同的顺序进行处理,尽量避免同时锁定两个资源,如操作A和B两张表时,总是按先A后B的顺序处理, 必须同时锁定两个资源时,要保证在任何时刻都应该按照相同的顺序来锁定资源。

    3、例子:

    public class TestDeadLock implements Runnable{
        public int flag = 0;
        static Object o1 = new Object();
        static Object o2 = new Object();
        @Override
        public void run() {
            if(flag == 0){
                synchronized (o1){
                    System.out.println("flag=0获取Object1的锁");
                    try{
                        Thread.sleep(1000);
                    }catch (InterruptedException e){
                        e.printStackTrace();
                    }
                    synchronized (o2){
                        System.out.println("flag=0获取Object2的锁");
                    }
                }
            }
            if(flag == 1){
                synchronized (o2){
                    System.out.println("flag=1获取Object2的锁");
                    try{
                        Thread.sleep(1000);
                    }catch (InterruptedException e){
                        e.printStackTrace();
                    }
                    synchronized (o1){
                        System.out.println("flag=1获取Object1的锁");
                    }
                }
            }
        }
        public static void main(String[] args) {
            TestDeadLock td1 = new TestDeadLock();
            TestDeadLock td2 = new TestDeadLock();
            td1.flag = 0;
            td2.flag = 1;
            new Thread(td1).start();
            new Thread(td2).start();
        }
    }
    

    Object1和Object2定为static,保证实例只有一份:

    输出结果:(产生死锁)

    flag=0获取Object1的锁
    flag=1获取Object2的锁
    。。。。。(程序发生死锁)
    

    上诉代码经过以下修改可以避免死锁的发生:

    public class TestDeadLock implements Runnable{
        public int flag = 0;
        static Object o1 = new Object();
        static Object o2 = new Object();
        @Override
        public void run() {
            if(flag == 0){
                synchronized (o1){
                    System.out.println("flag=0获取Object1的锁");
                    try{
                        Thread.sleep(1000);
                    }catch (InterruptedException e){
                        e.printStackTrace();
                    }
                }
                synchronized (o2){
                    System.out.println("flag=0获取Object2的锁");
                }
            }
            if(flag == 1){
                synchronized (o2){
                    System.out.println("flag=1获取Object2的锁");
                    try{
                        Thread.sleep(1000);
                    }catch (InterruptedException e){
                        e.printStackTrace();
                    }
                }
                synchronized (o1){
                    System.out.println("flag=1获取Object1的锁");
                }
            }
        }
        public static void main(String[] args) {
            TestDeadLock td1 = new TestDeadLock();
            TestDeadLock td2 = new TestDeadLock();
            td1.flag = 0;
            td2.flag = 1;
            new Thread(td1).start();
            new Thread(td2).start();
        }
    }
    

    输出结果:

    flag=0获取Object1的锁
    flag=1获取Object2的锁
    flag=1获取Object1的锁
    flag=0获取Object2的锁
    

    上述demo最常见,在占有一个资源的时候继续申请占有另外一个资源,这种情况下在其他线程试图申请前一个线程占有的资源时容易发生死锁。避免嵌套申请锁。

    二、并发修改同一记录

    1、出现原因:

    用户A查询一条纪录,然后修改该条纪录;这时用户B修改该条纪录,这时用户A的事务里锁的性质由查询的共享锁企图上升到独占锁,而用户B里的独占锁由于A有共享锁存在所以必须等A释放掉共享锁,而A由于B的独占锁而无法上升的独占锁也就不可能释放共享锁,于是出现了死锁。这种死锁由于比较隐蔽,但在稍大点的项目中经常发生。

    一般更新模式由一个事务组成,此事务读取记录,获取资源(页或行)的共享 (S) 锁,然后修改行,此操作要求锁转换为排它 (X) 锁。如果两个事务获得了资源上的共享模式锁,然后试图同时更新数据,则一个事务尝试将锁转换为排它 (X) 锁。共享模式到排它锁的转换必须等待一段时间,因为一个事务的排它锁与其它事务的共享模式锁不兼容;发生锁等待。第二个事务试图获取排它 (X) 锁以进行更新。由于两个事务都要转换为排它 (X) 锁,并且每个事务都等待另一个事务释放共享模式锁,因此发生死锁。

    2、解决方法:

    这种死锁比较常见,是由于程序的BUG产生的,除了调整的程序的逻辑没有其它的办法。仔细分析程序的逻辑,对于数据库的多表操作时,尽量按照相同的顺序进行处理,尽量避免同时锁定两个资源,如操作A和B两张表时,总是按先A后B的顺序处理, 必须同时锁定两个资源时,要保证在任何时刻都应该按照相同的顺序来锁定资源。

    a. 使用乐观锁进行控制。乐观锁大多是基于数据版本(Version)记录机制实现。即为数据增加一个版本标识,在基于数据库表的版本解决方案中,一般是通过为数据库表增加一个“version”字段来实现。读取出数据时,将此版本号一同读出,之后更新时,对此版本号加一。此时,将提交数据的版本数据与数据库表对应记录的当前版本信息进行比对,如果提交的数据版本号大于数据库表当前版本号,则予以更新,否则认为是过期数据。乐观锁机制避免了长事务中的数据库加锁开销(用户A和用户B操作过程中,都没有对数据库数据加锁),大大提升了大并发量下的系统整体性能表现。Hibernate 在其数据访问引擎中内置了乐观锁实现。需要注意的是,由于乐观锁机制是在我们的系统中实现,来自外部系统的用户更新操作不受我们系统的控制,因此可能会造成脏数据被更新到数据库中。
    b. 使用悲观锁进行控制。悲观锁大多数情况下依靠数据库的锁机制实现,如Oracle的Select … for update语句,以保证操作最大程度的独占性。但随之而来的就是数据库性能的大量开销,特别是对长事务而言,这样的开销往往无法承受。如一个金融系统,当某个操作员读取用户的数据,并在读出的用户数据的基础上进行修改时(如更改用户账户余额),如果采用悲观锁机制,也就意味着整个操作过程中(从操作员读出数据、开始修改直至提交修改结果的全过程,甚至还包括操作员中途去煮咖啡的时间),数据库记录始终处于加锁状态,可以想见,如果面对成百上千个并发,这样的情况将导致灾难性的后果。所以,采用悲观锁进行控制时一定要考虑清楚。
    c. SqlServer可支持更新锁

    为解决死锁,SqlServer引入更新锁,它有如下特征:

    (1) 加锁的条件:当一个事务执行update语句时,数据库系统会先为事务分配一把更新锁。
    (2) 解锁的条件:当读取数据完毕,执行更新操作时,会把更新锁升级为独占锁。
    (3) 与其他锁的兼容性:更新锁与共享锁是兼容的,也就是说,一个资源可以同时放置更新锁和共享锁,但是最多放置一把更新锁。这样,当多个事务更新相同的数据时,只有一个事务能获得更新锁,然后再把更新锁升级为独占锁,其他事务必须等到前一个事务结束后,才能获取得更新锁,这就避免了死锁。
    (4) 并发性能:允许多个事务同时读锁定的资源,但不允许其他事务修改它。

    例子如下:

    T1:
    begin tran
    select * from table(updlock) (加更新锁)
    update table set column1='hello'
    T2:
    begin tran
    select * from table(updlock)
    update table set column1='world'
    

    更新锁的意思是:“我现在只想读,你们别人也可以读,但我将来可能会做更新操作,我已经获取了从共享锁(用来读)到排他锁(用来更新)的资格”。一个事物只能有一个更新锁获此资格。
    T1执行select,加更新锁。
    T2运行,准备加更新锁,但发现已经有一个更新锁在那儿了,只好等。
    当后来有user3、user4…需要查询table表中的数据时,并不会因为T1的select在执行就被阻塞,照样能查询,提高了效率。

    三、索引不当导致全表扫描

    1、出现原因:

    如果在事务中执行了一条不满足条件的语句,执行全表扫描,把行级锁上升为表级锁,多个这样的事务执行后,就很容易产生死锁和阻塞。类似的情况还有当表中的数据量非常庞大而索引建的过少或不合适的时候,使得经常发生全表扫描,最终应用系统会越来越慢,最终发生阻塞或死锁。

    2、解决方法:

    SQL语句中不要使用太复杂的关联多表的查询;使用“执行计划”对SQL语句进行分析,对于有全表扫描的SQL语句,建立相应的索引进行优化。

    四、预防死锁

    死锁发生的条件:

    1、资源不能共享,需要只能由一个进程或者线程使用
    2、请求且保持,已经锁定的资源自给保持着不释放
    3、不剥夺,自给申请到的资源不能被别人剥夺
    4、循环等待

    防止死锁的途径就是避免满足死锁条件的情况发生,适合这个问题解决的方案有:

    1、保持事务简短并在一个批处理中
    在同一数据库中并发执行多个需要长时间运行的事务时通常发生死锁。事务运行时间越长,其持有排它锁或更新锁的时间也就越长,从而堵塞了其它活动并可能导致死锁。保持事务在一个批处理中,可以最小化事务的网络通信往返量,减少完成事务可能的延迟并释放锁。

    2、使用低隔离级别
    确定事务是否能在更低的隔离级别上运行。执行提交读允许事务读取另一个事务已读取(未修改)的数据,而不必等待第一个事务完成。使用较低的隔离级别(例如提交读)而不使用较高的隔离级别(例如可串行读)可以缩短持有共享锁的时间,从而降低了锁定争夺(比如这次的S NK和X IK 是InnoDB引擎Repeatable Read级别才有的)。

    展开全文
  • 在多道程序环境中,多个进程...或许,死锁的最好例证是 Kansas 立法机构在 20 世纪初通过的一项法律,其中说到“当两列列车在十字路口逼近时,它们应完全停下来,并且在一列列车开走之前另一列列车不能再次启动。...

    在多道程序环境中,多个进程可以竞争有限数量的资源。当一个进程申请资源时,如果这时没有可用资源,那么这个进程进入等待状态。有时,如果所申请的资源被其他等待进程占有,那么该等待进程有可能再也无法改变状态。这种情况称为死锁。

    或许,死锁的最好例证是 Kansas 立法机构在 20 世纪初通过的一项法律,其中说到“当两列列车在十字路口逼近时,它们应完全停下来,并且在一列列车开走之前另一列列车不能再次启动。”
    系统模型
    有一个系统拥有有限数量的资源,需要分配到若干竞争进程。这些资源可以分成多种类型,每种类型有一定数量的实例。资源类型有很多,如 CPU 周期、文件、I/O 设备(打印机和 DVD 驱动器)等。如果一个系统有两个 CPU,那么资源类型 CPU 就有两个实例。类似地,资源类型打印机可能有 5 个实例。

    如果一个进程申请某个资源类型的一个实例,那么分配这种类型的任何实例都可满足申请。否则,这些实例就不相同,并且资源分类没有定义正确。例如,一个系统有两台打印机。如果没有人关心哪台打印机打印哪些输出,那么这两台打印机可定义为属于同样的资源类型。然而,如果一台打印机在九楼,而另一台在底楼,那么九楼的用户就不会认为这两台打印机是相同的,这样每个打印机就可能需要定义成属于单独的类型。

    各种同步工具如互斥锁和信号量,也应作为系统资源,它们是常见的死锁源。然而,一个锁通常与保护某个特定的数据结构相关联,即一个锁可用于保护队列的访问,另一个锁保护访问链接列表的访问,等等。由于这个原因,每个锁通常有自己的资源类型,并且这种定义不是一个问题。

    进程在使用资源前应申请资源,在使用资源之后应释放资源。一个进程可能要申请许多资源,以便完成指定任务。显然,申请的资源数量不能超过系统所有资源的总和。换言之,如果系统只有两台打印机,那么进程就不能申请三台打印机。

    在正常操作模式下,进程只能按如下顺序使用资源:
    申请:进程请求资源。如果申请不能立即被允许(例如,申请的资源正在被其他进程使用),那么申请进程应等待,直到它能获得该资源为止。
    使用:进程对资源进行操作(例如,如果资源是打印机,那么进程就可以在打印机上打印了)。
    释放:进程释放资源。

    当进程或线程每次使用内核管理的资源时,操作系统会检查以确保该进程或线程已经请求并获得了资源。系统表记录每个资源是否是空闲的或分配的。对于每个已分配的资源,该表还记录了它被分配的进程。如果进程申请的资源正在为其他进程所使用,那么该进程会添加到该资源的等待队列上。

    当一组进程内的每个进程都在等待一个事件,而这一事件只能由这一组进程的另一个进程引起,那么这组进程就处于死锁状态。这里所关心的主要事件是资源的获取和释放。资源可能是物理资源(例如,打印机、磁带驱动器、内存空间和 CPU 周期)或逻辑资源(例如,信号量、互斥锁和文件)。然而,其他类型的事件也会导致死锁(例如 IPC 功能)。

    为说明死锁状态,假设一个系统具有三个 CD 刻录机。假定有三个进程,每个进程都占用了一台 CD 刻录机。如果每个进程现在需要另一台刻录机,那么这三个进程会处于死锁状态。每个进程都在等待事件“CD刻录机被释放”,这仅可能由一个等待进程来完成。这个例子说明了涉及同一种资源类型的死锁。

    死锁也可能涉及不同资源类型。例如,假设一个系统有一台打印机和一台 DVD 驱动器。假如进程 Pi 占有 DVD 驱动器而进程 P2 占有打印机。如果 Pi 申请打印机而 Pj 申请 DVD 驱动器,那么就会出现死锁。

    多线程应用程序的开发人员应始终警惕可能的死锁。多线程应用程序容易死锁,因为多线程可能竞争共享资源。
    死锁特征
    发生死锁时,进程永远不能完成,系统资源被阻碍使用,以致于阻止了其他作业开始执行。在讨论处理死锁问题的各种方法之前,我们首先深入讨论一下死锁特点。
    必要条件
    如果在一个系统中以下四个条件同时成立,那么就能引起死锁:
    互斥:至少有一个资源必须处于非共享模式,即一次只有一个进程可使用。如果另一进程申请该资源,那么申请进程应等到该资源释放为止。
    占有并等待:—个进程应占有至少一个资源,并等待另一个资源,而该资源为其他进程所占有。
    非抢占:资源不能被抢占,即资源只能被进程在完成任务后自愿释放。
    循环等待:有一组等待进程 {P0,P1,…,Pn},P0 等待的资源为 P1 占有,P1 等待的资源为 P2 占有,……,Pn-1 等待的资源为 Pn 占有,Pn 等待的资源为 P0 占有。
    我们强调所有四个条件必须同时成立才会出现死锁。循环等待条件意味着占有并等待条件,这样四个条件并不完全独立。
    资源分配图
    通过称为系统资源分配图的有向图可以更精确地描述死锁。该图包括一个节点集合 V 和一个边集合 E。节点集合 V 可分成两种类型:P={P1,p2,…,Pn}(系统所有活动进程的集合)和 R={R1,R2,…,Rm}(系统所有资源类型的集合)。

    从进程 Pi 到资源类型 Rj 的有向边记为 Pi->Rj,它表示进程 Pi 已经申请了资源类型 Rj 的一个实例,并且正在等待这个资源。从资源类型 Rj 到进程 Pi 的有向边记为 Rj->Pi,它表示资源类型 Rj 的一个实例已经分配给了进程 Pi。有向边 Pi->Rj 称为申请边,有向边 Rj->Pi 称为分配边。

    在图形上,用圆表示进程 Pi,用矩形表示资源类型 Rj。由于资源类型 Rj 可能有多个实例,所以矩形内的点的数量表示实例数量。注意申请边只指向矩形 Rj,而分配边应指定矩形内的某个圆点。

    当进程 Pi 申请资源类型 Rj 的一个实例时,就在资源分配图中加入一条申请边。当该申请可以得到满足时,那么申请边就立即转换成分配边。当进程不再需要访问资源时,它就释放资源,因此就删除了分配边。
    在这里插入图片描述

    图 1 资源分配图

    图 1 的资源分配图表示了如下情况:
    集合 P、R 和 E:
    P={P1,P2,P3}
    R={R1,R2,R3,R4}
    E={P1 -> R1,P2 -> R3,R1 -> P2,R2 -> P2,R2 -> P1,R3 -> P3}
    资源实例:
    资源类型 R1 有 1 个实例;
    资源类型 R2 有 2 个实例;
    资源类型 R3 有 1 个实例;
    资源类型 R4 有 3 个实例;
    进程状态:
    进程 P1 占有资源类型 R2 的 1 个实例,等待资源类型 R1 的 1 个实例。
    进程 P2 占有资源类型 R1 的 1 个实例和资源类型 R2 的 1 个实例,等待资源类型 R3 的 1 个实例。
    进程 P3 占有资源类型 R3 的 1 个实例。

    根据资源分配图的定义,可以证明:如果分配图没有环,那么系统就没有进程死锁。如果分配图有环,那么可能存在死锁。

    如果每个资源类型刚好有一个实例,那么有环就意味着已经出现死锁。如果环上的每个类型只有一个实例,那么就出现了死锁。环上的进程就死锁。在这种情况下,图中的环就是死锁存在的充分且必要条件。

    如果每个资源类型有多个实例,那么有环并不意味着已经出现了死锁。在这种情况下,图中的环就是死锁存在的必要条件而不是充分条件。

    为了说明这点,下面回到图 1 所示资源分配图。假设进程 P3 申请了资源类型 R2 的一个资源。由于现在没有资源实例可用,所以就增加了有向边 P3 -> R2(图 2)。
    在这里插入图片描述
    存在死锁的资源分配图
    图 2 存在死锁的资源分配图

    这时,系统有两个最小环:
    P1—> R1 —> P2 一> R3 —> P3 —> R2 —> P1
    P2 —> R3 —> P3 —> R2 —> P2

    进程 P1、P2 和 P3 死锁了。进程 P2 等待资源类型 R3,而它又被进程 R3 占有。进程 P3 等待进程 P1 或进程 P2 以释放资源类型 R2。另外,进程 P1 等待进程 P2 释放资源 R1。
    在这里插入图片描述
    具有环的并未死锁的资源分配图
    图 3 具有环的并未死锁的资源分配图

    现在考虑图 3 所示的资源分配图。在这个例子中,也有一个环:
    P1 —> R1 —> P3 —> R2 —> P1

    然而,并没有死锁。注意,进程 P4 可能释放资源类型 R2 的实例。这个资源可分配给进程 P3,从而打破环。

    总而言之,如果资源分配图没有环,那么系统就不处于死锁状态。如果有环,那么系统可能会也可能不会处于死锁状态。在处理死锁问题时,这点是很重要的。
    死锁处理方法
    一般来说,处理死锁问题有三种方法:
    通过协议来预防或避免死锁,确保系统不会进入死锁状态。
    可以允许系统进入死锁状态,然后检测它,并加以恢复。
    可以忽视这个问题,认为死锁不可能在系统内发生。

    第三种解决方案为大多数操作系统所采用,包括 Linux 和 Windows。因此,应用程序开发人员需要自己编写程序,以便处理死锁。

    接下来,我们简要阐述每种死锁处理方法。在进行之前,我们应该提一下,有些研究人员认为,这些基本方法不能单独用于处理操作系统的所有资源分配问题。然而,可以将这些基本方法组合起来,为每种系统资源选择一种最佳方法。

    为了确保死锁不会发生,系统可以采用死锁预防或死锁避免方案。死锁预防方法确保至少有一个必要条件不成立。这些方法通过限制如何申请资源的方法来预防死锁。

    死锁避免要求,操作系统事先得到有关进程申请资源和使用资源的额外信息。有了这些额外信息,系统可以确定对于每个申请,进程是否应等待。为了确定当前申请是允许还是延迟,系统应考虑现有的可用资源、已分配给每个进程的资源及每个进程将来申请和释放的资源。

    如果系统不使用死锁预防或死锁避免算法,那么死锁情况可能发生。在这种情况下,系统可以提供一个算法来检查系统状态以确定死锁是否发生,提供另一个算法来从死锁中恢复(如果死锁确实已经发生)。

    当没有算法用于检测和恢复死锁时,可能出现这样的情况,系统处于死锁,而又没有方法检测到底发生了什么。在这种情况下,未被发现的死锁会导致系统性能下降,因为资源被不能运行的进程占有,而越来越多的进程会因申请资源而进入死锁。最后,整个系统会停止工作,且需要人工重新启动。

    虽然这看起来似乎不是一个解决死锁问题的可行方法,但是它却为大多数操作系统所采用,许多系统死锁很少发生,因此与使用频繁的并且开销昂贵的死锁预防、死锁避免和死锁检测与恢复相比,这种方法更为便宜。

    此外,在有些情况下,系统处于冻结状态而不是死锁状态。例如,一个实时进程按最高优先级来运行(或其他进程在非抢占调用程序下运行),并且不将控制返回到操作系统。因此,系统应有人工方法可从这些状态中恢复过来,这些方法也可用于死锁恢复。

    展开全文
  • 怎么解决死锁问题

    2012-08-28 10:45:37
    insert into A select * from B 这样复制表数据会造成死锁吗? 该如何解决
  • 如果出现了死锁的情况,我们需要怎么解决呢?首先,我们来看下什么是死锁,下面这张图很形象的说明了什么是死锁,路口的四辆车都分别等待对面车道的车让行,相互等待,形成了死锁的状态。在线程中,死锁是这样定义...

    1.不小心死锁了怎么办?

    1.1什么是死锁?

    在使用线程的使用过程中,我们无法避免加锁的情况,那么加锁就可能会导致死锁。如果出现了死锁的情况,我们需要怎么去解决呢?首先,我们来看下什么是死锁,下面这张图很形象的说明了什么是死锁,路口的四辆车都分别等待对面车道的车让行,相互等待,形成了死锁的状态。在线程中,死锁是这样定义的,一组相互竞争相同资源的线程因为相互等待而导致永久阻塞,这种现象就叫做死锁。

    在这里插入图片描述

    1.2模拟死锁示例

    用一个转账的示例来模拟死锁。

    账户类:

    public class Account {
        private String accountName;
        private int balance;
    
        public Account(String accountName, int balance) {
            this.accountName = accountName;
            this.balance = balance;
        }
    
        public void debit(int amount) {
            this.balance -= amount;
        }
    
        public void cribit(int amount) {
            this.balance += amount;
        }
    
        public String getAccountName() {
            return accountName;
        }
    
        public void setAccountName(String accountName) {
            this.accountName = accountName;
        }
    
        public int getBalance() {
            return balance;
        }
    
        public void setBalance(int balance) {
            this.balance = balance;
        }
    }
    

    转账类:

    public class TransferAccount implements  Runnable{
        private Account fromAccount; // 转入账户
        private Account toAccount; // 转出账户
        private int amount;
    
        public TransferAccount(Account fromAccount, Account toAccount, int amount) {
            this.fromAccount = fromAccount;
            this.toAccount = toAccount;
            this.amount = amount;
        }
    
        @Override
        public void run() {
            while(true){
                synchronized (fromAccount){
                    synchronized (toAccount){
                        if(fromAccount.getBalance()>=amount){
                            fromAccount.debit(amount);
                            toAccount.cribit(amount);
                        }
                    }
                    System.out.println(fromAccount.getAccountName()+"--------------"+fromAccount.getBalance());
                    System.out.println(toAccount.getAccountName()+"--------------"+toAccount.getBalance());
                }
            }
        }
    }
    

    测试类:

    public class Test {
        public static void main(String[] args) {
            Account fromAccount = new Account("zhangsan",100000);
            Account toAccount = new Account("lisi",200000);
    
            Thread a = new Thread(new TransferAccount(fromAccount,toAccount,1));
            Thread b = new Thread(new TransferAccount(toAccount,fromAccount,1));
    
            a.start();
            b.start();
        }
    }
    

    当我们运行测试类后,会发现当执行到某个时候,控制台会停止输出,这个时候就是发生了死锁。

    在这里插入图片描述

    1.3发生死锁的原因

    发生死锁必需以下四个条件同时满足

    • 互斥,共享资源 X 和 Y 只能被一个线程占用;
    • 占有且等待,线程 T1 已经取得共享资源 X,在等待共享资源 Y 的时候,不释放共享资源 X;
    • 不可抢占,其他线程不能强行抢占线程 T1 占有的资源;
    • 循环等待,线程 T1 等待线程 T2 占有的资源,线程 T2 等待线程 T1 占有的资源,就是循环等待。

    1.4如何解决死锁问题

    上面说到发生死锁必须同时满足四个条件,那么,我们破坏其中一个条件,就可以解决死锁的问题。如果已经发生了死锁,一般没有什么好的方法来解决,只能通过重启应用,所以如果要解决死锁问题,最好的方式就是提前规避。
    首先,我们肯定不能去破坏第一个条件,锁的作用就是要互斥。所以,只能去破坏其他三个条件。

    1.4.1 解决方法一

    破坏第二个条件,占有且等待,那么我们可以一次性去申请所有资源,就不会存在等待的问题了。

    对上面的示例进行改进:

    新增类Allocator :

    public class Allocator {
        private List<Object> list = new ArrayList<>();
    
        /**
         * 申请资源的方法
         * @param from
         * @param to
         * @return
         */
        synchronized boolean apply(Object from,Object to){
            if(list.contains(from) || list.contains(to))return false;
            list.add(from);
            list.add(to);
            return true;
        }
    
        /**
         * 释放资源的方法
         * @param from
         * @param to
         * @return
         */
        synchronized void free(Object from,Object to){
            list.remove(from);
            list.remove(to);
        }
    }
    

    修改TransferAccount类:

    public class TransferAccount01 implements  Runnable{
        private Account fromAccount; // 转入账户
        private Account toAccount; // 转出账户
        private int amount;
        Allocator allocator;
    
        public TransferAccount01(Account fromAccount, Account toAccount, int amount,Allocator allocator) {
            this.fromAccount = fromAccount;
            this.toAccount = toAccount;
            this.amount = amount;
            this.allocator = allocator;
        }
    
        @Override
        public void run() {
            while(true){
                if(allocator.apply(fromAccount,toAccount)){ // 都会在这个地方去获取资源
                    try {
                        synchronized (fromAccount) {
                            synchronized (toAccount) {
                                if (fromAccount.getBalance() >= amount) {
                                    fromAccount.debit(amount);
                                    toAccount.cribit(amount);
                                }
                            }
                            System.out.println(fromAccount.getAccountName() + "--------------" + fromAccount.getBalance());
                            System.out.println(toAccount.getAccountName() + "--------------" + toAccount.getBalance());
                        }
                    }finally {
                        allocator.free(fromAccount,toAccount);
                    }
                }
            }
        }
    }
    

    测试类修改:

    public class Test {
        public static void main(String[] args) {
            Account fromAccount = new Account("zhangsan",100000);
            Account toAccount = new Account("lisi",200000);
            Allocator allocator = new Allocator(); // 统一分配锁
            Thread a = new Thread(new TransferAccount01(fromAccount,toAccount,1,allocator));
            Thread b = new Thread(new TransferAccount01(toAccount,fromAccount,1,allocator));
    
            a.start();
            b.start();
        }
    }
    

    经过这样处理之后就不会再出现死锁的问题了。

    1.4.2 解决方法二

    破坏不可抢占,可以让占用部分资源的线程进一步申请资源,如果申请不到,可以主动释放其他线程占用的资源,这样就可以把不可抢占的条件破坏掉。

    修改TransferAccount类:

    public class TransferAccount02 implements Runnable {
        private Account fromAccount; // 转入账户
        private Account toAccount; // 转出账户
        private int amount;
        Lock fromLock = new ReentrantLock();
        Lock toLock = new ReentrantLock();
    
        public TransferAccount02(Account fromAccount, Account toAccount, int amount) {
            this.fromAccount = fromAccount;
            this.toAccount = toAccount;
            this.amount = amount;
        }
    
        @Override
        public void run() {
            while (true) {
                if (fromLock.tryLock()) {
                    if (toLock.tryLock()) {
                        if (fromAccount.getBalance() >= amount) {
                            fromAccount.debit(amount);
                            toAccount.cribit(amount);
                        }
                        System.out.println(fromAccount.getAccountName() + "--------------" + fromAccount.getBalance());
                        System.out.println(toAccount.getAccountName() + "--------------" + toAccount.getBalance());
    
                    }
                }
            }
        }
    }
    

    测试类修改:

    public class Test {
        public static void main(String[] args) {
            Account fromAccount = new Account("zhangsan",100000);
            Account toAccount = new Account("lisi",200000);
            Thread a = new Thread(new TransferAccount02(fromAccount,toAccount,1));
            Thread b = new Thread(new TransferAccount02(toAccount,fromAccount,1));
            a.start();
            b.start();
        }
    }
    

    1.4.2 解决方法三

    破坏循环等待,可以按照顺序来进行资源的处理。

    这里仅对TransferAccount进行修改,对传进来的账户的hashcode进行排序以保证顺序。

    public class TransferAccount03 implements  Runnable{
        private Account fromAccount; // 转入账户
        private Account toAccount; // 转出账户
        private int amount;
    
        public TransferAccount03(Account fromAccount, Account toAccount, int amount) {
            this.fromAccount = fromAccount;
            this.toAccount = toAccount;
            this.amount = amount;
        }
    
        @Override
        public void run() {
            Account left = null;
            Account right = null;
            if(fromAccount.hashCode()>toAccount.hashCode()){
                left = toAccount;
                right = fromAccount;
            }
            while(true){
                synchronized (left){
                    synchronized (right){
                        if(fromAccount.getBalance()>=amount){
                            fromAccount.debit(amount);
                            toAccount.cribit(amount);
                        }
                    }
                    System.out.println(fromAccount.getAccountName()+"--------------"+fromAccount.getBalance());
                    System.out.println(toAccount.getAccountName()+"--------------"+toAccount.getBalance());
                }
            }
        }
    }
    

    但是在这个例子中会存在一个问题,最后变成总是从一个相同的账户转到另一个账号,最终有一个账户的余额为0.

    在这里插入图片描述

    展开全文
  • 死锁怎么解决

    2018-11-24 19:54:22
    锁是python提供的对线程控制的对象 有互斥锁、可重入锁、死锁(其他的不一一介绍了) 死锁:在线程间共享多个资源的时候,如果两个线程分别占有一...解决: 给互斥锁添加超时时间 程序设计时要尽量避免银行家算法 ...

    锁是python提供的对线程控制的对象
    有互斥锁、可重入锁、死锁(其他的不一一介绍了)

    死锁:在线程间共享多个资源的时候,如果两个线程分别占有一部分资源并且同时等待对方的资源,就会造成死锁

    解决:
    给互斥锁添加超时时间
    程序设计时要尽量避免银行家算法

    展开全文
  • 解决线程死锁的办法是什么? 如何避免死锁? 什么是线程死锁? 首先是一个线程需要多把锁,并发的时候多个线程同时被阻塞,它们中的一个或者全部都在等待某个资源被释放。由于线程被无限期地阻塞,因此程序不可能...
  • 点击上方“码农进阶之路”,选择“设为星标”回复“面经”获取面试资料面试题什么是线程死锁,?产生死锁的四个必要条件?解决线程死锁的办法是什么?如何避免死锁?什么是线程死锁?首先是一个线程需...
  • mysql死锁怎么解决

    万次阅读 2019-01-07 17:07:16
    1,查看数据库的隔离...终止(或撤销)系统中的一个或多个死锁进程,直至打破循环环路,使系统从死锁状态中解除出来。 (2)抢占资源。从一个或多个进程中抢占足够数量的资源,分配给死锁进程,以打破死锁状态。
  • MySQL数据死锁怎么解决 等待,直到超时(innodb_lock_wait_timeout=50s),自动回滚事务。 发起死锁检测,主动回滚一条事务,让其他事务继续执行(innodb_deadlock_detect=on). 由于性能原因,一般都是使用死锁...
  • 死锁怎么解决

    2020-12-31 14:21:58
    } } } } } 细粒度锁:可以提高并行度,性能优化的一个重要手段,但是可能会导致死锁死锁:一组互相竞争资源的线程因互相等待,导致永久阻塞的现象。 上述代码发生死锁的原因: 线程T1执行账户A转账户B的操作; ...
  • 死锁 在写c/c++的过程中遇到死锁常常会让我们手无足错,因为死锁往往不像其他类型的错误一样会在终端直接打印报错,因此难以被发现并需要大量精力去排错。是时候思考,该如何防范或者避免死锁。...解决途径1 如果使用
  • 死锁判定原理和具体场景,死锁怎么解决? 什么是锁? MySQL各存储引擎使用了三种类型(级别)的锁定机制:表级锁定,行级锁定和页级锁定。 1.表级锁定(table-level) 表级别的锁定是MySQL各存储引擎中最大颗粒...
  • 死锁的影响在不同系统中是不一样的,这取决于系统对死锁的处理能力。 数据库:检测并放弃事务。 JVM :无法自动处理。 发生死锁的例子 经典死锁 public class DeadLock implements Runnable { int flag = 1; sta...
  • 死锁

    2021-05-22 14:36:29
    怎么解决死锁 产生死锁的代码案例 synchronized 实现死锁以及解决办法 Lock 实现死锁以及解决办法 1、 我们为什么需要锁以及锁的概念 在单线程的时候,我们往往不需要考虑到锁的概念,因为单线程的时候,系统和...
  • 点击下方“IT牧场”,选择“设为星标”咱们使用 MySQL 大概率上都会遇到死锁问题,这实在是个令人非常头痛的问题。本文将会对死锁进行相应介绍,对常见的死锁案例进行相关分析与探讨,以及如何...
  • oracle死锁解决方法

    2018-06-11 16:02:54
    1、 关于死锁 死锁是指两个或两个以上的进程在执行过程中,由于竞争资源或者由于彼此通信而造成的一种...2、 怎么解决死锁 查看引起死锁的语句: select sql_text from v$sql where hash_value in (select sq...

空空如也

空空如也

1 2 3 4 5 ... 20
收藏数 502
精华内容 200
关键字:

怎么解决死锁