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

     一、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()方法:

Java死锁产生条件 java死锁产生的原因_死锁

     程序无法结束  

    4、死锁排查

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

   

Java死锁产生条件 java死锁产生的原因_死锁_02

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

     jstack  4860

   

Java死锁产生条件 java死锁产生的原因_后端_03

   可以查看到死锁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);

           执行结果

Java死锁产生条件 java死锁产生的原因_死锁_04

          查询成功,插入数据会报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;

        执行结果 如下

Java死锁产生条件 java死锁产生的原因_开发语言_05

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

SELECT  * FROM  t_buy  WHERE  id=1;

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

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

Java死锁产生条件 java死锁产生的原因_Java死锁产生条件_06

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

SHOW  GLOBAL  VARIABLES  LIKE  'innodb_lock_wait_timeout';

 

Java死锁产生条件 java死锁产生的原因_死锁_07

     (2)事务锁排查

查看当前的事务

SELECT * FROM INFORMATION_SCHEMA.INNODB_TRX;

Java死锁产生条件 java死锁产生的原因_死锁_08

kill  trx_mysql_thread_id

Java死锁产生条件 java死锁产生的原因_后端_09

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

查看当前锁定的事务

SELECT * FROM INFORMATION_SCHEMA.INNODB_LOCKS;

Java死锁产生条件 java死锁产生的原因_java_10

        查看当前等锁的事务

SELECT * FROM INFORMATION_SCHEMA.INNODB_LOCK_WAITS;

Java死锁产生条件 java死锁产生的原因_Java死锁产生条件_11

       合并查询:

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  ;