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< 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循环中,如果不能获取到锁就循环遍历。
本文的代码:
learn-java-base-9-to-20/tree/master/security
本文已收录于 http://www.flydean.com/java-security-code-line-dead-lock/
最通俗的解读,最深刻的干货,最简洁的教程,众多你不知道的小技巧等你来发现!
欢迎关注我的公众号:「程序那些事」,懂技术,更懂你!
java安全编码指南之:死锁dead lock的更多相关文章
- java安全编码指南之:lock和同步的正确使用
目录 简介 使用private final object来作为lock对象 不要synchronize可被重用的对象 不要sync Object.getClass() 不要sync高级并发对象 不要使 ...
- java安全编码指南之:基础篇
目录 简介 java平台本身的安全性 安全第一,不要写聪明的代码 在代码设计之初就考虑安全性 避免重复的代码 限制权限 构建可信边界 封装 写文档 简介 作为一个程序员,只是写出好用的代码是不够的,我 ...
- java安全编码指南之:Mutability可变性
目录 简介 可变对象和不可变对象 创建mutable对象的拷贝 为mutable类创建copy方法 不要相信equals 不要直接暴露可修改的属性 public static fields应该被置位f ...
- java安全编码指南之:字符串和编码
目录 简介 使用变长编码的不完全字符来创建字符串 char不能表示所有的Unicode 注意Locale的使用 文件读写中的编码格式 不要将非字符数据编码为字符串 简介 字符串是我们日常编码过程中使用 ...
- java安全编码指南之:输入校验
目录 简介 在字符串标准化之后进行校验 注意不可信字符串的格式化 小心使用Runtime.exec() 正则表达式的匹配 简介 为了保证java程序的安全,任何外部用户的输入我们都认为是可能有恶意攻击 ...
- java安全编码指南之:可见性和原子性
目录 简介 不可变对象的可见性 保证共享变量的复合操作的原子性 保证多个Atomic原子类操作的原子性 保证方法调用链的原子性 读写64bits的值 简介 java类中会定义很多变量,有类变量也有实例 ...
- java安全编码指南之:ThreadPool的使用
目录 简介 java自带的线程池 提交给线程池的线程要是可以被中断的 正确处理线程池中线程的异常 线程池中使用ThreadLocal一定要注意清理 简介 在java中,除了单个使用Thread之外,我 ...
- java安全编码指南之:线程安全规则
目录 简介 注意线程安全方法的重写 构造函数中this的溢出 不要在类初始化的时候使用后台线程 简介 如果我们在多线程中引入了共享变量,那么我们就需要考虑一下多线程下线程安全的问题了.那么我们在编写代 ...
- java安全编码指南之:文件IO操作
目录 简介 创建文件的时候指定合适的权限 注意检查文件操作的返回值 删除使用过后的临时文件 释放不再被使用的资源 注意Buffer的安全性 注意 Process 的标准输入输出 InputStream ...
随机推荐
- Linux操作命令和工具使用
lrzsz是一个unix通信套件提供的X,Y,和ZModem文件传输协议. windows 需要向centos服务器上传文件,可直接在centos上执行命令yum -y install lrzsz 程 ...
- Java面试题(反射篇+对象拷贝篇)
反射 57.什么是反射? 反射主要是指程序可以访问.检测和修改它本身状态或行为的一种能力 Java反射: 在Java运行时环境中,对于任意一个类,能否知道这个类有哪些属性和方法?对于任意一个对象,能否 ...
- 读取手机中的联系人信息(android.provider.ContactsContract)
本篇开始讲如何从Android中得到本机联系人的信息.由于Android较快的版本升级,部分API已经发生了变化.本篇探究的通过ContentProvider机制获取联系人的API从Android2. ...
- Inno Setup Compiler 中文使用教程
一.概要 该文章主要解决,Inno Setup Compiler工具的使用问题. 如有什么建议欢迎提出,本人及时修改.[如有任何疑惑可以加Q群:580749909] 二.步骤 (1)下载地址:http ...
- 使用IDEA创建一个SSM工程(非maven)
说在前面的话 直到现在从Eclipse向IDEA转的人越来越多,但是IDEA的项目创建让人摸不清头脑,因此这里我创建一个非maven的ssm工程,供大家练练手,进一步的了解IDEA在项目中的使用. 创 ...
- Spring Security如何优雅的增加OAuth2协议授权模式
一.什么是OAuth2协议? OAuth 2.0 是一个关于授权的开放的网络协议,是目前最流行的授权机制. 数据的所有者告诉系统,同意授权第三方应用进入系统,获取这些数据.系统从而产生一个短期的进入令 ...
- 【MarkDown】github readme添加图片 Markdown语法添加图片,适用各种markdown语法
作者:程序员小冰,CSDN博客:http://blog.csdn.net/qq_21376985, QQ986945193 微博:http://weibo.com/mcxiaobing 首先给大家看一 ...
- 小程序开发-开发模式下关闭http域名校验
小程序开发模式去掉域名校验 我们在开发小程序的时候如果用到其他网络地址,在小程序运行时调试器会输出 : http://www.example.com 不在以下 request 合法域名列表中,请参考文 ...
- 为什么 char 数组比 String 更适合存储密码?
推荐阅读:5 个刁钻的 String 面试题! 另一个基于 String 的棘手 Java 问题,相信我只有很少的 Java 程序员可以正确回答这个问题. 这是一个真正艰难的核心 Java 面试问题, ...
- leetcode刷题-53最大子序和
题目 给定一个整数数组 nums ,找到一个具有最大和的连续子数组(子数组最少包含一个元素),返回其最大和. 思路 动态规划:求整个数组的连续子数组的最大和,可以求出每个位置的连续子数组的最大和,返回 ...