一、什么是死锁
并发编程的本质是将串行执行的代码编程并行执行。并发编程的目的是为了加快程序的运行速度,但是如果使用不当,不仅不会带来速度的提升,反而变得更慢,甚至造成程序出现异常。其中,死锁就是一种对并发编程带来的挑战。
什么是死锁?
死锁是指多个线程循环等待他方占有的资源而无限制地僵持下去的局面。如果没有外力的作用,那么死锁涉及到的各个线程都将永远处于阻塞状态。如下代码时一个死锁的经典例子:
public class DeadLockDemo {
privat static String A = "A";
private static String B = "B";
public static void main(String[] args) {
new DeadLockDemo().deadLock();
}
private void deadLock() {
Thread t1 = new Thread(new Runnable() {
@Override
publicvoid run() {
synchronized (A) {
try {
Thread.currentThread().sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
}
synchronized (B) {
System.out.println("1");
}
}
}
});
Thread t2 = new Thread(new Runnable() {
@Override
publicvoid run() {
synchronized (B) {
synchronized (A) {
System.out.println("2");
}
}
}
});
t1.start();
t2.start();
}
}
这段代码只是演示死锁的场景,在现实中可能不会写出这样的代码。但是,在一些更为复杂的场景中,你可能会遇到这样的问题,比如t1拿到锁之后,因为一些异常情况没有释放锁(死循环)。又或者是t1拿到一个数据库锁,释放锁的时候抛出了异常,没释放掉。
还有一个经典的业务场景:比如银行转账,在转账执行中,你可能会先锁住转出的账户,将款扣掉,然后锁住转入的账户,将金额增加。从表面上看,我们总是先锁转出账户,然后锁住转入账户,应该是没有问题的。但是张三向李四转钱,转出账户是张三的账户,转入的账户是李四,这一线程先获取张三的账户为对象锁,然后再获取李四的账户为对象锁。同时李四向张三转钱,转出账户是李四的账户,转入的账户是张三的账户,这一线程需要先获取李四的账户的账户为对象锁,然后获取张三的账户为对象锁,张三的账户为对象锁的锁已经被获取了。上述过程就会造成死锁。
二、如何避免死锁?
1、避免一个线程同时获取多个锁。
2、避免一个线程在锁内同时占用多个资源,尽量保证每个锁只占用一个资源。
3、尝试使用定时锁,使用lock.tryLock(timeout)来替代使用内部锁机制。
4、对于数据库锁,加锁和解锁必须在一个数据库连接里,否则会出现解锁失败的情况。
网友评论