死锁机制

  • 介绍: 死锁是由于不同的进程拥有对方需要的被锁资源,又在相互请求对方的被锁资源造成的无限等待局面

1 JAVA中的死锁

1.1 死锁示例

两个死锁类

 1 package deadlock;
 2 
 3 import java.util.List;
 4 
 5 public class A extends Thread {
 6     private List listA; //A,B两个类共享同一个listA和listB
 7     private List listB;
 8 
 9     public A(List listA, List listB) {
10         this.listA = listA;
11         this.listB = listB;
12     }
13 
14     public void lockListA() {
15         synchronized (listA) {  //请求listA的对象锁
16             System.out.println("listA locked");
17             try {
18                 sleep(2 * 1000);    //休息2秒
19             } catch (InterruptedException e) {
20                 e.printStackTrace();
21             }
22             synchronized (listB) {  //请求listB的对象锁
23                 listB.add("B");
24                 System.out.println(listB.toString());
25             }
26         }
27         System.out.println("listA unlocked");
28 
29     }
30 
31     public void run() {
32         lockListA();
33     }
34 }
 1 package deadlock;
 2 
 3 import java.util.List;
 4 
 5 public class B extends Thread{
 6     private List listA;
 7     private List listB;
 8 
 9     public B(List listA, List listB) {
10         this.listA = listA;
11         this.listB = listB;
12     }
13 
14     public void lockListB() {
15         synchronized (listB) {
16             System.out.println("listB locked");
17             try {
18                 sleep(5 * 1000);
19             } catch (InterruptedException e) {
20                 e.printStackTrace();
21             }
22             synchronized (listA) {
23                 listA.add("A");
24                 System.out.println(listA.toString());
25             }
26         }
27         System.out.println("listB unlocked");
28 
29     }
30 
31     public void run() {
32         lockListB();
33     }
34 }

运行主方法

 1 package deadlock;
 2 
 3 import java.util.ArrayList;
 4 import java.util.List;
 5 
 6 public class Test {
 7     public static void main(String[] args) {
 8         List a = new ArrayList();
 9         List b = new ArrayList();
10 
11         A threadA = new A(a, b);
12         B threadB = new B(a, b);
13 
14         threadA.start();
15         threadB.start();
16     }
17 }

运行结果: 

listA locked
listB locked

 可以看到locked以后就不能unlocked了,也就是进入了死锁,听我分析:

  1. threadA.start()调用了lockListA()方法,这时候listA的对象锁被threadA获取,然后threadA进入sleep 2秒的过程
  2. threadB.start()调用了lockListB()方法,这时候listB的对象锁被threadB获取,然后threadB进入sleep 5秒的过程
  3. 2秒后,threadA醒来,threadA请求listB的对象锁,此时threadB处于sleep状态,listB的对象锁被threadB持有,所以threadA坐等threadB释放对象锁
  4. 又过了3秒,threadB醒来,threadB请求listA的对象锁,此时threadA处于等待阻塞状态(正在等threadB释放listB的对象锁),还没有释放listA的锁,所以threadB也只有等待threadA释放listA的对象锁,于是threadB也进入阻塞状态
  5. 于是,threadA和threadB都进入了阻塞,于是就无限阻塞下去了...

1.2 同步方法的常用写法

一般来说,我们在写线程安全的方法时都会这样写,如下:

 1 package threadsafe;
 2 
 3 import java.util.ArrayList;
 4 import java.util.List;
 5 
 6 public class ThreadSafeClass {
 7     List<Integer> intList;
 8 
 9     public ThreadSafeClass() {
10         intList = new ArrayList<Integer>();
11     }
12 
13     public void addElement() {
14         synchronized (this) {   //调用此方法是,将自己锁死
15             Integer elem = (int)(Math.random() * 100);
16             intList.add(elem);
17         }
18     }
19 }
 1 package threadsafe;
 2 
 3 import java.util.ArrayList;
 4 import java.util.List;
 5 
 6 public class MyThread extends Thread{
 7     ThreadSafeClass threadSafeClass;
 8 
 9     public MyThread(ThreadSafeClass tsc) {
10         threadSafeClass = tsc;
11     }
12 
13     public void run() {
14         threadSafeClass.addElement();   //多线程执行此方法只能一步一步来,因为要获取本对象锁
15     }
16 }

1.4 JAVA自己实现的线程安全类

1.4.1 Vector一族

先谈谈Vector和ArrayList,我们都知道Vector和ArrayList功能类似,他们的区别在于Vector是线程安全的,而ArrayList不是,那么Vector的线程安全是如何实现的呢

查看源码可以发现他的方法多是synchronized的(这里我就不贴源码了,有兴趣的同学可以去查查),也就是说他的实现和我上面说的类似,这种方法的缺点其实就是效率比较低下,因为加锁是很消耗效率的

1.4.2 ConccurentLinkedQueue

这里以ConcurrentLinkedQueue为例,查看源码看不到synchronized之类的东西,这里引用网摘的一段话:

先来回顾之前提到过的ConcurrentHashMap,它是一个以Concurrent开头的并发集合类,其原理是通过增加锁和细化锁的粒度来提高并发度。
另一个值得一提的Concurrent是ConcurrentLinkedQueue。这个类采用了另一种提高并发度的方式:非阻塞算法(Non-blocking),第一次实现了无锁的并发。

也就是说ConcurrentLinkedQueue并不是采用加锁的方式实现线程同步的,这个非阻塞算法大家有兴趣可以去研究一下

2 数据库中的死锁

数据库中的死锁常出现在事物中,这里我不写具体的代码了,举个例子随便聊聊

比如数据库中有两个表

现在又两个事务:

事务A:

1 BEGIN;
2 
3 SELECT * FROM user WHERE userId = ? FOR UPDATE;
4 
5 DELETE FROM message WHERE messageId = ?;
6 
7 COMMIT;

事务B:

1 BEGIN;
2 
3 SELECT * FROM message WHERE messageId = ? FOR UPDATE;
4 
5 UPDATE user SET userName = 'test' WHERE userId = ?;
6 
7 COMMIT;

假设现在两个事务同时运行,userId = 1, messageId = 1,就会造成死锁

因为事务A首先对userId=1的那行加上行锁,事务B也同时对messageId=1的那行加上行锁

然后事务A想DELETE messageId=1的那行,就必须等待事务B结束,释放messageId=1的那行的行锁

而事务B想UPDATE userId=1的那行,也必须等待事务A结束,释放userId=1的那行的行锁

这样相互等待,就死锁了

原文地址:https://www.cnblogs.com/zemliu/p/2503725.html