死锁原因及解决方案

      在应用程序开发的过程中,有时候线上会遇到死锁问题,死锁一般有操作系统级别的死锁和应用程序级别的死锁,操作系统级别的死锁通常发生的是进程死锁,应用程序级别的死锁通常是线程的死锁,本文主要谈谈线程死锁问题。

一、java线程死锁

1、死锁的原因

       (1) 因竞争资源发生死锁 现象:系统中供多个线程共享的资源的数目不足以满足全部进程的需要时,就会引起对诸资源的竞争而发生死锁现象

        (2) 推进顺序不当发生死锁

2、线程死锁的条件

     (1)互斥条件:进程对所分配到的资源不允许其他进程进行访问,若其他进程访问该资源,只能等待,直至占有该资源的进程使用完成后释放该资源

     (2)请求和保持条件:进程获得一定的资源之后,又对其他资源发出请求,但是该资源可能被其他进程占有,此事请求阻塞,但又对自己获得的资源保持不放

     (3)不可剥夺条件:是指进程已获得的资源,在未完成使用之前,不可被剥夺,只能在使用完后自己释放

    (4)环路等待条件:是指进程发生死锁后,必然存在一个进程--资源之间的环形链

3、java 线程死锁示例

    public  static class LockObject {

        private int p;

        private boolean v;

        public LockObject(int p, boolean v) {
            this.p = p;
            this.v = v;
        }

     
    }
 public  static  class DeadLockThread{

        private final LockObject  lockObjectA=new LockObject(1, false);

        private final  LockObject  lockObjectB=new LockObject(2,true);


        public   void fetch(){
            new Thread(() -> {
                fetchB();
            }, "threadA").start();

            new Thread(() -> {
                fetchA();
            }, "threadB").start();
        }


        public  void  fetchB(){
            String name = Thread.currentThread().getName();
            synchronized (lockObjectA){
                LOGGER.info("线程"+name+"进入.....给lockObjectA上锁成功");
                try {
                    TimeUnit.SECONDS.sleep(3);
                    LOGGER.info("线程"+name+"开始.....给lockObjectB上锁");
                    synchronized (lockObjectB){
                        LOGGER.info("线程"+name+"进入.....给lockObjectB上锁成功");
                    }
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
            LOGGER.info("线程A进入.....执行完成");
        }


        public  void  fetchA(){
            String name = Thread.currentThread().getName();
            synchronized (lockObjectB){
                LOGGER.info("线程"+name+"进入.....给lockObjectB上锁成功");
                try {
                    TimeUnit.SECONDS.sleep(3);
                    LOGGER.info("线程"+name+"开始.....给lockObjectA上锁");
                    synchronized (lockObjectA){
                        LOGGER.info("线程"+name+"进入.....给lockObjectA上锁成功");
                    }
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
            LOGGER.info("线程"+name+"进入.....执行完成");
        }

    }


          main方法调用 fetch()方法:

      程序无法结束  

  4、死锁排查

       jps  -l  查询正在运行的java进程

   

     使用jdk自带工具jstack查看线程运行状况

      jstack  4860

   

          可以查看到死锁deadlock的信息

二、数据库死锁

         此次以msyql数据库为例,研究mysql死锁问题,先创建一张表

DROP TABLE IF EXISTS `t_buy`;
CREATE TABLE `t_buy`  (
  `id` int(11) NOT NULL AUTO_INCREMENT,
  `user_id` int(11) NULL DEFAULT NULL COMMENT '用户id',
  `name` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL,
  `goods` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL,
  `price` decimal(10, 2) NULL DEFAULT NULL,
  `status` int(1) NULL DEFAULT NULL COMMENT '0 下单  1 支付',
  `update_time` datetime(0) NULL DEFAULT NULL,
  PRIMARY KEY (`id`) USING BTREE
) ENGINE = InnoDB AUTO_INCREMENT = 1 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_general_ci ROW_FORMAT = Dynamic;

SET FOREIGN_KEY_CHECKS = 1;

1、数据库锁库和解锁

      (1)锁库,此时全局锁库,数据库里所有的表只可读,不可写

     FLUSH TABLES WITH READ LOCK ;

           分别执行读数据和写数据

SELECT  *  FROM  t_buy;

INSERT  INTO  t_buy(user_id,name,goods,price,`status`) VALUES (101,'LiuPing','苹果',23.00,0);

           执行结果

          查询成功,插入数据会报read lcok错误

       (2)解锁

      UNLOCK TABLES ;

2、锁表、解锁

     (1)操作命令

     LOCK TABLES t_buy;

     UNLOCK TABLES ;

       (2) 锁表排查

            查看表状态

   SHOW STATUS LIKE 'table%';

   SHOW STATUS LIKE 'innodb_row_lock%';

   #非常有用的排查手段,可有效的分析出锁表、事务锁
   SET  GLOBAL  innodb_status_output_locks = 1;
   SHOW ENGINE INNODB STATUS ;

            查询是否有锁表

      SHOW OPEN TABLES where In_use > 0;

            查询进程、杀死进程

   SHOW PROCESSLIST

   KILL id

 3、事务锁

     (1)事务锁演示    

关闭事务自动提交

SHOW GLOBAL  VARIABLES  LIKE 'autocommit';

SET  GLOBAL  autocommit = 0;

        打开一个会话1,执行修改行操作,不提交事务

BEGIN;
UPDATE  t_buy  SET  name='西瓜'  WHERE  id=1;

        执行结果 如下

       打开会话2,对上面的sql在操作一次


SELECT  * FROM  t_buy  WHERE  id=1;

UPDATE   t_buy  SET  name='荔枝'  WHERE  id=1;

       执行修改,我们可以发现,会话一直阻塞,无法提交,知道120s后抛出事务锁异常

        为什么会是120后抛出错误,这是因为数据库的事务锁超时时间为120s

SHOW  GLOBAL  VARIABLES  LIKE  'innodb_lock_wait_timeout';

 

        以上是针对修改条件的字段是主键索引,事务锁锁的是一行记录即为行锁。有时候条件是范围修改,那么行锁会变成间隙锁,如果果修改条件字段没有命中索引,那么行锁或间隙锁会升级为表锁。

     (2)事务锁排查

         查看当前的事务

   SELECT * FROM INFORMATION_SCHEMA.INNODB_TRX;

       对于正在运行未提交的事务,如果阻塞了其他会话,可以使用  kill  trx_mysql_thread_id 回滚事务, 释放事务锁。

        KILL  15  ,杀掉当前的RUNING事务锁,就可以解决事务锁等待问题

        查看当前锁定的事务

    SELECT * FROM INFORMATION_SCHEMA.INNODB_LOCKS;

        查看当前等锁的事务

    SELECT * FROM INFORMATION_SCHEMA.INNODB_LOCK_WAITS;

        合并查询:

SELECT
	a.trx_state,
	b.event_name,
	a.trx_started,
	b.timer_wait / 1000000000000 timer_wait,
	a.trx_mysql_thread_id blocking_trx_id,
	b.sql_text 
FROM
	information_schema.innodb_trx a,
	PERFORMANCE_SCHEMA.events_statements_current b,
	PERFORMANCE_SCHEMA.threads c 
WHERE
	a.trx_mysql_thread_id = c.processlist_id 
	AND b.thread_id = c.thread_id;

     查看行锁意向锁

   SELECT  *     FROM      `performance_schema`.data_locks  ;


 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值