浅谈java安全编码指南之死锁dead lock

 更新时间:2021年06月03日 10:19:52   作者:flydean  
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< transferAmount){
                    System.out.println("余额不足!");
                }else{
                    amount=amount-transferAmount;
                    target.amount=target.amount+transferAmount;
                }
            }
        }
    }
}

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

为了保证在transfer的过程中,两个账户不被别人修改,我们使用了两个synchronized关键字,分别把transfer对象和目标对象进行锁定。

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

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();

上面的例子中,我们定义了两个account,然后两个账户互相转账,最后很有可能导致互相锁定,最后产生死锁。

使用private类变量

使用两个sync会有顺序的问题,那么有没有办法只是用一个sync就可以在所有的实例中同步呢?

有的,我们可以使用private的类变量,因为类变量是在所有实例中共享的,这样一次sync就够了:

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 < transferAmount) {
                System.out.println("余额不足!");
            } else {
                amount = amount - transferAmount;
                target.amount = target.amount + transferAmount;
            }
        }
    }
}

使用相同的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。

在做transfer的时候,我们先比较两个对象的ID大小,然后根据ID进行排序,最后安装顺序进行加锁。这样就能够保证顺序,从而避免死锁。

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

    if (compareTo(target) < 0) {
        fist = this;
        second = target;
    } else {
        fist = target;
        second = this;
    }

    synchronized (fist){
        synchronized (second){
            if(amount< transferAmount){
                System.out.println("余额不足!");
            }else{
                amount=amount-transferAmount;
                target.amount=target.amount+transferAmount;
            }
        }
    }
}

释放掉已占有的锁

死锁是互相请求对方占用的锁,但是对方的锁一直没有释放,我们考虑一下,如果获取不到锁的时候,自动释放已占用的锁是不是也可以解决死锁的问题呢?

因为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< transferAmount){
                                System.out.println("余额不足!");
                            }else{
                                amount=amount-transferAmount;
                                target.amount=target.amount+transferAmount;
                            }
                            break;
                        } finally {
                            target.lock.unlock();
                        }
                    }
                } finally {
                    this.lock.unlock();
                }
            }
            //随机sleep一定的时间,保证可以释放掉锁
            Thread.sleep(1000+new Random(1000L).nextInt(1000));
        }
    }

}

我们把两个tryLock方法在while循环中,如果不能获取到锁就循环遍历。

以上就是浅谈java安全编码指南之死锁dead lock的详细内容,更多关于java安全编码指南之死锁dead lock的资料请关注脚本之家其它相关文章!

相关文章

  • 使用Logback设置日志级别

    使用Logback设置日志级别

    这篇文章主要介绍了使用Logback设置日志级别的操作,具有很好的参考价值,希望对大家有所帮助。如有错误或未考虑完全的地方,望不吝赐教
    2021-07-07
  • Servlet实现简单文件上传功能

    Servlet实现简单文件上传功能

    这篇文章主要为大家详细介绍了Servlet实现简单文件上传功能,具有一定的参考价值,感兴趣的小伙伴们可以参考一下
    2017-10-10
  • springmvc @ResponseStatus和ResponseEntity的使用

    springmvc @ResponseStatus和ResponseEntity的使用

    这篇文章主要介绍了springmvc @ResponseStatus和ResponseEntity的使用方式,具有很好的参考价值,希望对大家有所帮助,如有错误或未考虑完全的地方,望不吝赐教
    2024-07-07
  • 浅谈springboot的三种启动方式

    浅谈springboot的三种启动方式

    这篇文章主要介绍了浅谈springboot的三种启动方式,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧
    2019-09-09
  • springboot整合vue2-uploader实现文件分片上传、秒传、断点续传功能

    springboot整合vue2-uploader实现文件分片上传、秒传、断点续传功能

    对于大文件的处理,无论是用户端还是服务端,如果一次性进行读取发送、接收都是不可取,很容易导致内存问题,下面这篇文章主要给大家介绍了关于springboot整合vue2-uploader实现文件分片上传、秒传、断点续传功能的相关资料,需要的朋友可以参考下
    2023-06-06
  • 深入解析Java类加载的案例与实战教程

    深入解析Java类加载的案例与实战教程

    本篇文章主要介绍Tomcat类加载器架构,以及基于类加载和字节码相关知识,去分析动态代理的原理,对Java类加载相关知识感兴趣的朋友一起看看吧
    2022-05-05
  • Lucene 索引删除策略源码解析

    Lucene 索引删除策略源码解析

    这篇文章主要为大家介绍了Lucene 索引删除策略源码解析,有需要的朋友可以借鉴参考下,希望能够有所帮助,祝大家多多进步,早日升职加薪
    2023-03-03
  • 微信公众平台开发实战Java版之微信获取用户基本信息

    微信公众平台开发实战Java版之微信获取用户基本信息

    这篇文章主要介绍了微信公众平台开发实战Java版之微信获取用户基本信息 的相关资料,需要的朋友可以参考下
    2015-12-12
  • SpringBoot实现EMQ设备的上下线告警

    SpringBoot实现EMQ设备的上下线告警

    EMQX 的上下线系统消息通知功能在客户端连接成功或者客户端断开连接,需要实现设备的上下线状态监控,所以本文给大家介绍了如何通过SpringBoot实现EMQ设备的上下线告警,文中有详细的代码示例,需要的朋友可以参考下
    2023-10-10
  • MyBatis insert标签及常用属性详解

    MyBatis insert标签及常用属性详解

    这篇文章主要介绍了MyBatis insert标签,insert 标签中没有 resultType 属性,只有查询操作才需要对返回结果类型进行相应的指定,需要的朋友可以参考下
    2023-10-10

最新评论