Java 编码机制:死锁dead lock

简介

在java中,为了保证共享数据的安全性,我们引入了锁机制。有了锁,可能会发生死锁。

死锁是由于多个线程锁定了彼此需要的资源,然后没有释放已有的资源,导致循环等待。

一般来说,如果不同的线程不匹配锁定和释放锁的顺序,就很可能发生死锁。

不同的加锁顺序

例子

public class DiffLockOrder {

    private int amount;

    public DiffLockOrder(int amount){
       this.amount=amount;
    }

    public void transfer(DiffLockOrder target,int transferAmount){
        synchronized (this){
            synchronized (target){
                if(amount

在上面的例子中,我们模拟了一个转账过程,金额用来表示用户余额。转账用于将当前账号的部分金额转移到目标对象。

为了保证两个账号在转账过程中不会被他人修改,我们使用两个同步的关键字分别锁定转账对象和目标对象。

看起来没有问题,但是我们没有考虑到,在调用的过程中,转移的顺序可以发送变化:

        DiffLockOrder account1 = new DiffLockOrder(1000);
        DiffLockOrder account2 = new DiffLockOrder(500);

        Runnable target1= ()->account1.transfer(account2,200);
        Runnable target2= ()->account2.transfer(account1,100);
        new Thread(target1).start();
        new Thread(target2).start();

在上面的例子中,我们定义了两个账户,然后两个账户互相转账,最终可能会导致互相锁定和死锁。

使用private类变量

当使用两个同步时会有顺序问题,那么有没有办法在所有情况下只用一个同步来同步呢?

是的,我们可以使用私有类变量,因为类变量在所有实例中都是共享的,所以一次同步就足够了:

public class LockWithPrivateStatic {

    private int amount;

    private static final Object lock = new Object();

    public LockWithPrivateStatic(int amount){
       this.amount=amount;
    }

    public void transfer(LockWithPrivateStatic target, int transferAmount){
        synchronized (lock) {
            if (amount 

使用相同的Order

我们死锁的原因是我们不能控制锁定顺序。如果能控制锁定顺序,就不会出现死锁了吗?

根据这个想法,我们向对象添加另一个id字段:

    private final long id; // 唯一ID,用来排序
    private static final AtomicLong nextID = new AtomicLong(0); // 用来生成ID

    public DiffLockWithOrder(int amount){
       this.amount=amount;
        this.id = nextID.getAndIncrement();
    }

初始化对象时,我们使用static的AtomicLong类为每个对象生成一个惟一的ID。

在做转移的时候,我们先比较两个对象的ID大小,然后按照ID排序,最后按照安装顺序锁定。这样可以保证顺序,避免死锁。

    public void transfer(DiffLockWithOrder target, int transferAmount){
        DiffLockWithOrder fist, second;

        if (compareTo(target) 

释放掉已占有的锁

死锁是相互请求对方占用的锁,但对方的锁并没有被释放。我们来考虑一下,如果锁得不到,自动释放被占用的锁是否也能解决死锁问题?

因为ReentrantLock有一个tryLock()方法,所以我们可以用这个方法来判断锁是否可以被获取,如果不能获取就释放被占用的锁。

我们使用ReentrantLock来完成这个例子:

public class DiffLockWithReentrantLock {

    private int amount;
    private final Lock lock = new ReentrantLock();

    public DiffLockWithReentrantLock(int amount){
        this.amount=amount;
    }

    private void transfer(DiffLockWithReentrantLock target, int transferAmount)
            throws InterruptedException {
        while (true) {
            if (this.lock.tryLock()) {
                try {
                    if (target.lock.tryLock()) {
                        try {
                            if(amount//随机sleep一定的时间,保证可以释放掉锁
            Thread.sleep(1000+new Random(1000L).nextInt(1000));
        }
    }

}

我们将两个tryLock方法放在一个while循环中,如果我们不能获得锁,我们将遍历它。

本文章来源于互联网,如有侵权,请联系删除!

相关推荐: 创新微ESP32-C3低功耗WIFI模块 品质值得信赖

发展的新时代已经到来,作为物联网产业重要的部分,智能家居逐渐成为行业中汹涌的浪潮。物联网即“万物相连的互联网”,是一个基于互联网、传统电信网等的信息承载体。物联网智能家居的传输技术有GPRS,LORA,WiFi,蓝牙等多种传输方式,现在我们来聊的是一款物联网智…