当您有两个正在互相等待释放资源锁的进程时,就会发生死锁。

假设我们在Java应用程序中有2个线程:

  • 线程1获得对资源A的锁定线程2获得对资源B的锁定为了继续执行(并释放对资源A的锁定),线程1等待直到资源B释放为止为了继续执行(并释放对资源B的锁定),线程2等待直到资源A释放为止

这两个线程都无法完成执行,而我们的应用程序已陷入僵局。

您实际上可以在MySQL表上亲自尝试:



mysql> CREATE TABLE a (id int primary key, value int);
Query OK, 0 rows affected (0.01 sec)

mysql> INSERT INTO a VALUES (1, 0), (2, 0), (3, 0), (4, 0);
Query OK, 4 rows affected (0.01 sec)
Records: 4  Duplicates: 0  Warnings: 0

mysql console 1:
step 1> START TRANSACTION;
step 3> UPDATE a SET value = 1 WHERE id = 2;
step 5> UPDATE a SET value = 1 WHERE id = 1;

mysql console 2:
step 2> START TRANSACTION;
step 4> UPDATE a SET value = 1 WHERE id = 1;
step 6> UPDATE a SET value = 1 WHERE id = 2;

ERROR 1213 (40001): Deadlock found when trying to get lock; try restarting transaction

mysql> CREATE TABLE a (id int primary key, value int);
Query OK, 0 rows affected (0.01 sec)

mysql> INSERT INTO a VALUES (1, 0), (2, 0), (3, 0), (4, 0);
Query OK, 4 rows affected (0.01 sec)
Records: 4  Duplicates: 0  Warnings: 0

mysql console 1:
step 1> START TRANSACTION;
step 3> UPDATE a SET value = 1 WHERE id = 2;
step 5> UPDATE a SET value = 1 WHERE id = 1;

mysql console 2:
step 2> START TRANSACTION;
step 4> UPDATE a SET value = 1 WHERE id = 1;
step 6> UPDATE a SET value = 1 WHERE id = 2;

ERROR 1213 (40001): Deadlock found when trying to get lock; try restarting transaction



最近,我正在研究有此问题的多线程应用程序。 这是一个具有16个线程的Java应用程序。 每个线程将每1ms更新一次MySQL表中的10行。 在MySQL中更新一行时,将在该特定行上设置排他锁-阻止任何其他事务对其进行更新。 这种情况很少发生,但是有时2个线程会互相等待释放行锁,从而导致死锁。

我考虑了几种不同的解决方案:

  • 如果发生死锁,请重新启动事务。问题:我将使用陈旧的数据更新行如果发生死锁,请重新启动整个过程。问题:这可能会使应用变慢。在尝试更新每个线程之前,请使其“声明”为一行。 问题:这会增加应用程序的复杂性,并降低性能

在继续之前,我休息了一会儿,仔细考虑了死锁的原因。 所有线程均在同一个MySQL表上执行10个更新。 死锁发生的唯一方法是,如果顺序和时间匹配非常特定的模式。 我无法控制时间,但可以控制更新顺序。

实际上,如果我以相同的方式(例如,通过行ID的升序)对所有更新进行排序,则可以消除出现死锁的可能性。

但是,为什么更新总是以随机顺序发生? 事实证明,该应用程序从数据库中获取100行并选择10行进行更新。 但是,为了防止16个线程总是更新相同的10行,它将首先对100行的列表进行洗牌。 这意味着10次更新将始终以某种随机顺序进行。

我修改了应用程序,以便在更新10行之前,它将按行ID的升序对更新进行排序。 我部署了应用程序,这才是僵局的终结。

死锁可以通过许多不同的方式发生,但是在这种情况下,获得的教训是:

如果要在多线程应用程序中锁定资源,请确保所有线程都以相同的顺序锁定(基于资源的属性的顺序)。

简单来说:

如果要在多线程应用程序中更新行,请确保所有线程都按行ID的顺序(升序或降序)更新行。