18.透彻理解死锁
Posted 纵横千里,捭阖四方
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了18.透彻理解死锁相关的知识,希望对你有一定的参考价值。
synchronized同步锁,虽然能解决线程安全的问题,但是如果使用不当,就可能导致死锁,也即请求被阻塞而一直无法返回。
除了死锁,还有个活锁的情况,我们看一下概念的区别:
-
死锁: 一组互相竞争资源的线程因互相等待,导致“永久”阻塞的现象。
-
活锁: 活锁指的是任务或者执行者没有被阻塞,由于某些条件没有满足,导致一直重复尝试—失败—尝试—失败的过程。也就是“生不如死”的状态,不过处于活锁的实体是在不断的改变状态,活锁有可能自行解开。
这四个条件同时满足,就会产生死锁。
-
互斥,共享资源 X 和 Y 只能被一个线程占用;
-
占有且等待,线程 T1 已经取得共享资源 X,在等待共享资源 Y 的时候,不释放共享资源 X;
-
不可抢占,其他线程不能强行抢占线程 T1 占有的资源;
-
循环等待,线程 T1 等待线程 T2 占有的资源,线程 T2 等待线程 T1 占有的资源,就是循环等待。
1 构造一个死锁
接下来,我们构造一个简单的死锁程序来分析一下这个场景。
public class DeadLockDemo
private static String A = "A";
private static String B = "B";
public static void main(String[] args)
new DeadLockDemo().deadLock();
private void deadLock()
Thread t1 = new Thread(new Runnable()
@Override
public void run()
synchronized (A)
try
Thread.currentThread().sleep(200);
catch (InterruptedException e)
e.printStackTrace();
synchronized (B)
System.out.println("1");
);
Thread t2 = new Thread(new Runnable()
@Override
public void run()
synchronized (B)
try
Thread.currentThread().sleep(200);
catch (InterruptedException e)
e.printStackTrace();
synchronized (A)
System.out.println("2");
);
t1.start();
t2.start();
这个例子中,线程1 先给A加锁,然后就睡觉了,之后再给B加锁才能执行完。线程2,则给B加锁后就睡觉,之后再给A加锁才能执行完。所以这里两个线程就死锁了。 执行之后检验一下情况:使用jps 查看进程号,然后使用jstack 查看,结果如下:
Found one Java-level deadlock:
=============================
"Thread-1":
waiting to lock monitor 0x04ddd074 (object 0x0f39ae88, a java.lang.String),
which is held by "Thread-0"
"Thread-0":
waiting to lock monitor 0x04dddd94 (object 0x0f39aea8, a java.lang.String),
which is held by "Thread-1"
Java stack information for the threads listed above:
===================================================
"Thread-1":
at com.liuqingchao.threadTest.DeadLockDemo$2.run(DeadLockDemo.java:38)
- waiting to lock <0x0f39ae88> (a java.lang.String)
- locked <0x0f39aea8> (a java.lang.String)
at java.lang.Thread.run(Thread.java:748)
"Thread-0":
at com.liuqingchao.threadTest.DeadLockDemo$1.run(DeadLockDemo.java:22)
- waiting to lock <0x0f39aea8> (a java.lang.String)
- locked <0x0f39ae88> (a java.lang.String)
at java.lang.Thread.run(Thread.java:748)
Found 1 deadlock.
上面的内容说人话就是: 找到一个java级别的死锁: Thread1 锁了0x0f39aea8 ,等待去锁 object 0x0f39ae88,但是这个被Thread 0 持有 Thread0 锁了0x0f39ae88,等待去锁 object 0x0f39aea8,这个被 Thread1 持有 所以就死锁了。
2 死锁预防措施
要预防死锁,只要破坏掉其4条规则的任何一个都可以。因此我们有多种策略来解除死锁。 1.针对互斥,重写代码逻辑。调整资源分配策略,先尝试都能拿到资源再去占有,例如:
public class Allocator
private List<Object> list=new ArrayList<>();
synchronized boolean apply(Object from,Object to)
if(list.contains(from)||list.contains(to))
return false;
list.add(from);
list.add(to);
return true;
synchronized void free(Object from,Object to)
list.remove(from);
list.remove(to);
2.不用synchronized,而使用ReentrantLock,加锁的时候使用fromLock.tryLock(),解决条件2 和3。
public class TransferAccount implements Runnable
private Account fromAccount; //转出账户
private Account toAccount; //转入账户
private int amount;
Lock fromLock = new ReentrantLock();
Lock toLock = new ReentrantLock();
public TransferAccount(Account fromAccount, Account toAccount, int amount)
this.fromAccount = fromAccount;
this.toAccount = toAccount;
this.amount = amount;
@Override
public void run()
while (true)
if (fromLock.tryLock()) //返回true和false
if (toLock.tryLock()) //返回true和false
if (fromAccount.getBalance() >= amount)
fromAccount.debit(amount);
toAccount.credit(amount);
//转出账户的余额
System.out.println(fromAccount.getName() + "->" + fromAccount.getBalance());
//转入账户的余额
System.out.println(toAccount.getName() + "->" + toAccount.getBalance());
public static void main(String[] args)
Account fromAccount = new Account("tt", 100000);
Account toAccount = new Account("ff", 300000);
Thread a = new Thread(new TransferAccount(fromAccount, toAccount, 10));
Thread b = new Thread(new TransferAccount(toAccount, fromAccount, 30));
a.start();
b.start();
3.除此之外,我们还可以破坏循环等待条件,每个都是按照顺序进行加锁等等。
以上是关于18.透彻理解死锁的主要内容,如果未能解决你的问题,请参考以下文章