• 18.透彻理解死锁


    synchronized同步锁,虽然能解决线程安全的问题,但是如果使用不当,就可能导致死锁,也即请求被阻塞而一直无法返回。

    除了死锁,还有个活锁的情况,我们看一下概念的区别:

    • 死锁: 一组互相竞争资源的线程因互相等待,导致“永久”阻塞的现象。

    • 活锁: 活锁指的是任务或者执行者没有被阻塞,由于某些条件没有满足,导致一直重复尝试—失败—尝试—失败的过程。也就是“生不如死”的状态,不过处于活锁的实体是在不断的改变状态,活锁有可能自行解开。

    这四个条件同时满足,就会产生死锁。

    • 互斥,共享资源 X 和 Y 只能被一个线程占用;

    • 占有且等待,线程 T1 已经取得共享资源 X,在等待共享资源 Y 的时候,不释放共享资源 X;

    • 不可抢占,其他线程不能强行抢占线程 T1 占有的资源;

    • 循环等待,线程 T1 等待线程 T2 占有的资源,线程 T2 等待线程 T1 占有的资源,就是循环等待。

    1 构造一个死锁

    接下来,我们构造一个简单的死锁程序来分析一下这个场景。

    1. public class DeadLockDemo {
    2. private static String A = "A";
    3. private static String B = "B";
    4. public static void main(String[] args) {
    5. new DeadLockDemo().deadLock();
    6. }
    7. private void deadLock() {
    8. Thread t1 = new Thread(new Runnable() {
    9. @Override
    10. public void run() {
    11. synchronized (A) {
    12. try {
    13. Thread.currentThread().sleep(200);
    14. } catch (InterruptedException e) {
    15. e.printStackTrace();
    16. }
    17. synchronized (B) {
    18. System.out.println("1");
    19. }
    20. }
    21. }
    22. });
    23. Thread t2 = new Thread(new Runnable() {
    24. @Override
    25. public void run() {
    26. synchronized (B) {
    27. try {
    28. Thread.currentThread().sleep(200);
    29. } catch (InterruptedException e) {
    30. e.printStackTrace();
    31. }
    32. synchronized (A) {
    33. System.out.println("2");
    34. }
    35. }
    36. }
    37. });
    38. t1.start();
    39. t2.start();
    40. }
    41. }

    这个例子中,线程1 先给A加锁,然后就睡觉了,之后再给B加锁才能执行完。线程2,则给B加锁后就睡觉,之后再给A加锁才能执行完。所以这里两个线程就死锁了。 执行之后检验一下情况:使用jps 查看进程号,然后使用jstack 查看,结果如下:

    1. Found one Java-level deadlock:
    2. =============================
    3. "Thread-1":
    4. waiting to lock monitor 0x04ddd074 (object 0x0f39ae88, a java.lang.String),
    5. which is held by "Thread-0"
    6. "Thread-0":
    7. waiting to lock monitor 0x04dddd94 (object 0x0f39aea8, a java.lang.String),
    8. which is held by "Thread-1"
    9. Java stack information for the threads listed above:
    10. ===================================================
    11. "Thread-1":
    12. at com.liuqingchao.threadTest.DeadLockDemo$2.run(DeadLockDemo.java:38)
    13. - waiting to lock <0x0f39ae88> (a java.lang.String)
    14. - locked <0x0f39aea8> (a java.lang.String)
    15. at java.lang.Thread.run(Thread.java:748)
    16. "Thread-0":
    17. at com.liuqingchao.threadTest.DeadLockDemo$1.run(DeadLockDemo.java:22)
    18. - waiting to lock <0x0f39aea8> (a java.lang.String)
    19. - locked <0x0f39ae88> (a java.lang.String)
    20. at java.lang.Thread.run(Thread.java:748)
    21. Found 1 deadlock.

    上面的内容说人话就是: 找到一个java级别的死锁: Thread1 锁了0x0f39aea8 ,等待去锁 object 0x0f39ae88,但是这个被Thread 0 持有 Thread0 锁了0x0f39ae88,等待去锁 object 0x0f39aea8,这个被 Thread1 持有 所以就死锁了。

    2 死锁预防措施

    要预防死锁,只要破坏掉其4条规则的任何一个都可以。因此我们有多种策略来解除死锁。 1.针对互斥,重写代码逻辑。调整资源分配策略,先尝试都能拿到资源再去占有,例如:

    1. public class Allocator {
    2. private List<Object> list=new ArrayList<>();
    3. synchronized boolean apply(Object from,Object to){
    4. if(list.contains(from)||list.contains(to)){
    5. return false;
    6. }
    7. list.add(from);
    8. list.add(to);
    9. return true;
    10. }
    11. synchronized void free(Object from,Object to){
    12. list.remove(from);
    13. list.remove(to);
    14. }
    15. }

    2.不用synchronized,而使用ReentrantLock,加锁的时候使用fromLock.tryLock(),解决条件2 和3。

    1. public class TransferAccount implements Runnable {
    2. private Account fromAccount; //转出账户
    3. private Account toAccount; //转入账户
    4. private int amount;
    5. Lock fromLock = new ReentrantLock();
    6. Lock toLock = new ReentrantLock();
    7. public TransferAccount(Account fromAccount, Account toAccount, int amount) {
    8. this.fromAccount = fromAccount;
    9. this.toAccount = toAccount;
    10. this.amount = amount;
    11. }
    12. @Override
    13. public void run() {
    14. while (true) {
    15. if (fromLock.tryLock()) { //返回true和false
    16. if (toLock.tryLock()) {//返回true和false
    17. if (fromAccount.getBalance() >= amount) {
    18. fromAccount.debit(amount);
    19. toAccount.credit(amount);
    20. }
    21. }
    22. }
    23. //转出账户的余额
    24. System.out.println(fromAccount.getName() + "->" + fromAccount.getBalance());
    25. //转入账户的余额
    26. System.out.println(toAccount.getName() + "->" + toAccount.getBalance());
    27. }
    28. }
    29. public static void main(String[] args) {
    30. Account fromAccount = new Account("tt", 100000);
    31. Account toAccount = new Account("ff", 300000);
    32. Thread a = new Thread(new TransferAccount(fromAccount, toAccount, 10));
    33. Thread b = new Thread(new TransferAccount(toAccount, fromAccount, 30));
    34. a.start();
    35. b.start();
    36. }
    37. }

    3.除此之外,我们还可以破坏循环等待条件,每个都是按照顺序进行加锁等等。

  • 相关阅读:
    (续)SSM整合之spring笔记(IOC 基于注解的自动装配@Autowired)(P091—P093)
    分布式搭载博客网站
    51.【结构体初始化的两种方法】
    个人博客类网站为什么更适合虚拟主机?
    1.4 内网穿透与通知、查询用户订单
    MLOps:掌握机器学习部署:Docker、Kubernetes、Helm 现代 Web 框架
    rust 快速一览
    python项目实战——银行取款机系统(二)
    客户流失?来看看大厂如何基于spark+机器学习构建千万数据规模上的用户留存模型 ⛵
    IntelliJ IDEA 安装 GitHub Copilot插件 (最新)
  • 原文地址:https://blog.csdn.net/xueyushenzhou/article/details/126691760