简介
在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,蓝牙等多种传输方式,现在我们来聊的是一款物联网智…