为了高并发下的线程安全,有时我们需要给线程加锁,当有两个锁A和B
线程1得到了锁A,在锁A未释放时,希望获得锁B
线程2获得了锁B,在锁B未释放时,希望获得锁A
两种情况同时发生就会产生死锁
public class DeadLock implements Runnable {
private Account accountA;
private Account accountB;
public DeadLock(Account accountA, Account accountB) {
this.accountA = accountA;
this.accountB = accountB;
}
@Override
public void run() {
synchronized (accountA) {
try {
System.out.println(1);
accountA.setAcount(new BigDecimal(1));
Thread.sleep(5000);
} catch (InterruptedException e) {
e.printStackTrace();
}
synchronized (accountB) {
System.out.println(2);
accountB.setAcount(new BigDecimal(1));
System.out.println(3);
}
}
}
public static void main(String[] args) {
Account accountA = new Account("1");
Account accountB = new Account("2");
new Thread(new DeadLock(accountA, accountB)).start();
new Thread(new DeadLock(accountB, accountA)).start();
}
}
@Data
public class Account {
private String accountId;
private BigDecimal acount;
public Account (String accountId){
this.accountId=accountId;
}
}
死锁发生的条件苛刻:
(
1
) 互斥条件:一个资源每次只能被一个进程使用。
(
2
) 请求与保持条件:一个进程因请求资源而阻塞时,对已获得的资源保持不放。
(
3
) 不剥夺条件:进程已获得的资源,在末使用完之前,不能强行剥夺。
(
4
) 循环等待条件:若干进程之间形成一种头尾相接的循环等待资源关系。
似乎我们在编程的时候避免锁嵌套,就可以避免死锁,但在我们实际情景中,我们可能对一个加锁的事务里使用了另一个加锁的事务,而且刚好线程相互占用了资源。发生的可能性极低,但是,可能会发生,一旦发生,造成的后果很可怕,业务将无法继续进行,所以使用在编程时需要多加注意,避免死锁的可能。
最简单的不要在锁里再请求锁,直接全局加锁:
public class DeadLock5 implements Runnable {
private Account accountA;
private Account accountB;
private Object lock=new Object();
public DeadLock5(Account accountA, Account accountB) {
this.accountA = accountA;
this.accountB = accountB;
}
@Override
public void run() {
synchronized (lock) {
try {
System.out.println(1);
accountA.setAcount(new BigDecimal(1));
Thread.sleep(5000);
} catch (InterruptedException e) {
e.printStackTrace();
}
synchronized (accountB) {
System.out.println(2);
accountB.setAcount(new BigDecimal(1));
System.out.println(3);
}
}
}
public static void main(String[] args) {
Account accountA = new Account("1");
Account accountB = new Account("2");
new Thread(new DeadLock5(accountA, accountB)).start();
new Thread(new DeadLock5(accountB, accountA)).start();
}
}
也可以规定特定的顺序加锁,比方通过Account的accountId中最小的元素加锁,就不会出现死锁了:
public class DeadLock1 implements Runnable {
private Account accountA;
private Account accountB;
public DeadLock1(Account accountA, Account accountB) {
this.accountA = accountA;
this.accountB = accountB;
}
@Override
public void run() {
String acountIdA = accountA.getAccountId();
String acountIdB = accountB.getAccountId();
String acountId;
if (Integer.valueOf(acountIdA)> Integer.valueOf(acountIdB)) {
acountId = acountIdB;
} else {
acountId = acountIdA;
}
synchronized (acountId) {
try {
System.out.println(1);
accountA.setAcount(new BigDecimal(1));
Thread.sleep(5000);
} catch (InterruptedException e) {
e.printStackTrace();
}
synchronized (acountId) {
System.out.println(2);
accountB.setAcount(new BigDecimal(1));
System.out.println(3);
}
}
}
public static void main(String[] args) {
Account accountA = new Account("1");
Account accountB = new Account("2");
new Thread(new DeadLock1(accountA, accountB)).start();
new Thread(new DeadLock1(accountB, accountA)).start();
}
}
也可以使用ReentrantLock
使用lock1.tryLock()方法来防止死锁。tryLock尝试获取锁,获取成功返回true,失败就返回false。
public class DeadLock2 implements Runnable {
private Account accountA;
private Account accountB;
private final static Lock lock1 = new ReentrantLock(true);
private final static Lock lock2 = new ReentrantLock(true);
public DeadLock2(Account accountA, Account accountB) {
this.accountA = accountA;
this.accountB = accountB;
}
@Override
public void run() {
while (true) {
if (lock1.tryLock()) {
try {
accountA.setAcount(new BigDecimal(1));
Thread.sleep(2000);
System.out.println(Thread.currentThread().getName()+":已成功获取 lock1 ...");
//如果获取成功则执行业务逻辑,如果获取失败,则释放lock1的锁,自旋重新尝试获得锁
if (lock2.tryLock()) {
try {
accountB.setAcount(new BigDecimal(1));
System.out.println(Thread.currentThread().getName()+":已成功获取 lock2 ...");
break;
} finally {
lock2.unlock();
}
}
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
lock1.unlock();
}
}
System.out.println(Thread.currentThread().getName()+":获取锁失败,重新获取---");
try {
//防止发生活锁
TimeUnit.NANOSECONDS.sleep(new Random().nextInt(100));
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
使用lock1.tryLock(long timeout, TimeUnit unit) 方法来防止多线程死锁。与lock1.tryLock()类似,区别在于该方法在拿不到锁时会等待一定的时间,在时间期限之内如果还拿不到锁,就返回false。如果一开始拿到锁或者在等待期间内拿到了锁,则返回true。
public class DeadLock3 implements Runnable {
private Account accountA;
private Account accountB;
private final static Lock lock1 = new ReentrantLock(true);
private final static Lock lock2 = new ReentrantLock(true);
public DeadLock3(Account accountA, Account accountB) {
this.accountA = accountA;
this.accountB = accountB;
}
@Override
public void run() {
while (true) {
try {
if (lock1.tryLock(10, TimeUnit.MILLISECONDS)) {
try {
accountA.setAcount(new BigDecimal(1));
Thread.sleep(2000);
System.out.println(Thread.currentThread().getName()+":已成功获取 lock1 ...");
//如果获取成功则执行业务逻辑,如果获取失败,则释放lock1的锁,自旋重新尝试获得锁
if (lock2.tryLock(10, TimeUnit.MILLISECONDS)) {
try {
accountB.setAcount(new BigDecimal(1));
System.out.println(Thread.currentThread().getName()+":已成功获取 lock2 ...");
break;
} finally {
lock2.unlock();
}
}
} finally {
lock1.unlock();
}
}
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName()+":获取锁失败,重新获取---");
try {
//防止发生活锁
TimeUnit.NANOSECONDS.sleep(new Random().nextInt(100));
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
public static void main(String[] args) {
Account accountA = new Account("1");
Account accountB = new Account("2");
new Thread(new DeadLock3(accountA, accountB)).start();
new Thread(new DeadLock3(accountB, accountA)).start();
}
}
使用lock1.lockInterruptibly()获得锁,如果发生死锁,调用线程interrupt来消除死锁。
ReentrantLock.lockInterruptibly允许在等待时由其它线程调用等待线程的Thread.interrupt方法来中断等待线程的等待而直接返回,这时不用获取锁,而会抛出一个InterruptedException。而ReentrantLock.lock方法不允许Thread.interrupt中断,即使检测到Thread.isInterrupted,一样会继续尝试获取锁,失败则继续休眠。只是在最后获取锁成功后再把当前线程置为interrupted状态。
public class DeadLock4 implements Runnable {
private Account accountA;
private Account accountB;
private final static Lock lock1 = new ReentrantLock(true);
private final static Lock lock2 = new ReentrantLock(true);
public DeadLock4(Account accountA, Account accountB) {
this.accountA = accountA;
this.accountB = accountB;
}
@Override
public void run() {
try {
lock1.lockInterruptibly();
accountA.setAcount(new BigDecimal(1));
Thread.sleep(2000);
System.out.println(Thread.currentThread().getName()+":已成功获取 lock1 ...");
try {
lock2.lockInterruptibly();
accountB.setAcount(new BigDecimal(1));
System.out.println(Thread.currentThread().getName()+":已成功获取 lock2 ...");
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
lock2.unlock();
}
} catch (InterruptedException e1) {
e1.printStackTrace();
} finally {
lock1.unlock();
}
}
public static void main(String[] args) {
Account accountA = new Account("1");
Account accountB = new Account("2");
new Thread(new DeadLock4(accountA, accountB)).start();
new Thread(new DeadLock4(accountB, accountA)).start();
}
}