前面说过使用同步机制可以解决多线程安全问题,不过使用同步也是有代价的,比如有如下两个缺点:1、效率低下;2、在有同步嵌套的时候容易出现死锁。那么什么是死锁呢?死锁就是指有两个或多个线程在相互争夺资源的过程中发生的一种相互等待的现象。
java中发生死锁有如下四个必要条件:
1、互斥使用,即当资源被一个线程占有时。别的线程不能使用;
2、不可抢占,资源请求者不能强制从资源占有者手中夺取资源,资源只能由资源占有者主动释放;
3、请求和保持,即当资源请求者在请求其他资源的同时保持对原有资源的占有;
4、循环等待,即存在一个等待队列:P1占有P2的资源,P2占有P3的资源。这样就形成了一个等待环路。
当以上四个条件都成立时,便形成了死锁。下面分别用synchronized和Lock锁模拟死锁产生的情况。
创建一个锁对象类MyLock
package com.gk.thread.deadlock;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
public class MyLock {
// 使用synchronized修饰代码块的时候锁对象可以是任意的,这里使用字符串来代替
public static String strLockA = new String();
public static String strLockB = new String();
public static final Lock lockA = new ReentrantLock();
public static final Lock lockB = new ReentrantLock();
}
synchronized实现:
package com.gk.thread.deadlock.synchronization;
import com.gk.thread.deadlock.MyLock;
public class DeadLock extends Thread {
private boolean flag;
private String name;
public DeadLock(String name, boolean flag) {
this.name = name;
this.flag = flag;
}
@Override
public void run() {
if(flag) {
synchronized(MyLock.strLockA) { // if行1
System.out.println(name + "锁住strLockA..."); // if行2
synchronized(MyLock.strLockB) { // if行3
System.out.println(name + "锁住strLockB..."); // if行4
}
}
}else {
synchronized(MyLock.strLockB) { // else行1
System.out.println(name + "锁住strLockB..."); // else行2
synchronized(MyLock.strLockA) { // else行3
System.out.println(name + "锁住strLockA..."); // else行4
}
}
}
}
}
测试代码:
package com.gk.thread.deadlock.synchronization;
public class Test {
public static void main(String[] args) {
// true执行DeadLock类run方法的if语句
new DeadLock("线程A", true).start();
// false执行DeadLock类run方法的else语句
new DeadLock("线程B", false).start();
}
}
代码解析:
DeadLock类的flag标记是用来人为控制不同线程执行不同路径,当flag为true时执行if块,当flag为false时执行else块。在测试代码中我们创建两个线程:线程A和线程B,并让线程A为true执行if块,让线程B为false执行else块。在if中首先锁住strLockA,然后锁住strLockB,else正好相反。
有这样一种情况:线程A进入了if块执行if行1这条语句,然后strLockA就被线程A锁住了,此时正好线程B进入了else块执行了else行1这条语句,然后strLockB就被线程B锁住了。当线程A要往下执行if行3锁住strLockB的时候,发现strLockB已经被占用还没释放,此时,线程A就一直等待strLockB能够被释放以供自己使用;同理线程B要往下执行else行3锁住strLockA的时候也发现strLockA被占用,也在等待strLockA能被释放。就这样两个线程都在等待其他线程能够释放自己所需资源而陷入了无限等待状态,从而就产生了死锁现象。
注意看控制台输出的时候小方块一直是红色的,说明程序一直在运行着而不会结束。
Lock锁实现:
package com.gk.thread.deadlock.lock;
import com.gk.thread.deadlock.MyLock;
public class DeadLock extends Thread{
private String name;
private boolean flag;
public DeadLock(String name, boolean flag) {
this.name = name;
this.flag = flag;
}
@Override
public void run() {
if(flag) { /// if
MyLock.lockA.lock();
try {
System.out.println(name + "锁住了lockA...");
}catch (Exception e) {
/*
* 在catch块中释放锁,
* 如果没有异常或异常不匹配导致catch块不能执行,
* 从而导致锁不能及时释放
*/
MyLock.lockA.unlock();
System.out.println(name + "释放了lockA...");
}
/
MyLock.lockB.lock();
try {
System.out.println(name + "锁住了lockB...");
}catch (Exception e) {
MyLock.lockB.unlock();
System.out.println(name + "释放了lockB...");
}
}else { /// else
MyLock.lockB.lock();
try {
System.out.println(name + "锁住了lockB...");
}catch (Exception e) {
MyLock.lockB.unlock();
System.out.println(name + "释放了lockB...");
}
/
MyLock.lockA.lock();
try {
System.out.println(name + "锁住了lockA...");
}catch (Exception e) {
MyLock.lockA.unlock();
System.out.println(name + "释放了lockA...");
}
}
}
}
测试代码:
package com.gk.thread.deadlock.lock;
public class Test {
public static void main(String[] args) {
// lock锁测试
new DeadLock("线程A", true).start();
new DeadLock("线程B", false).start();
}
}
代码解析:
相信认真看了上面对synchronized代码解析的,对Lock的理解应该不难。一般认为Lock出现死锁的原因是因为代码不规范引起的,比如上面代码中释放锁的代码lock.unlock()不应该放在catch块,而应该放在finally块中确保锁能被及时的释放。