- 介绍: 死锁是由于不同的进程拥有对方需要的被锁资源,又在相互请求对方的被锁资源造成的无限等待局面
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了,也就是进入了死锁,听我分析:
- threadA.start()调用了lockListA()方法,这时候listA的对象锁被threadA获取,然后threadA进入sleep 2秒的过程
- threadB.start()调用了lockListB()方法,这时候listB的对象锁被threadB获取,然后threadB进入sleep 5秒的过程
- 2秒后,threadA醒来,threadA请求listB的对象锁,此时threadB处于sleep状态,listB的对象锁被threadB持有,所以threadA坐等threadB释放对象锁
- 又过了3秒,threadB醒来,threadB请求listA的对象锁,此时threadA处于等待阻塞状态(正在等threadB释放listB的对象锁),还没有释放listA的锁,所以threadB也只有等待threadA释放listA的对象锁,于是threadB也进入阻塞状态
- 于是,threadA和threadB都进入了阻塞,于是就无限阻塞下去了...
1.2 同步方法的常用写法
一般来说,我们在写线程安全的方法时都会这样写,如下:
1 package threadsafe; 2 3 import java.util.ArrayList; 4 import java.util.List; 5 6 public class ThreadSafeClass { 7 ListintList; 8 9 public ThreadSafeClass() {10 intList = new ArrayList ();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的那行的行锁
这样相互等待,就死锁了