java死锁和解决方案

为了高并发下的线程安全,有时我们需要给线程加锁,当有两个锁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();
    }
}

 

 

 

 

 

 

 

  • 4
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值