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