首页 > 编程语言 >[Java并发]避免死锁

[Java并发]避免死锁

时间:2024-09-28 22:49:45浏览次数:1  
标签:Java System 并发 死锁 println new public out

破坏死锁的四个条件
银行家算法
按顺序请求资源
尽量避免嵌套锁
尝试锁定trylock

避免死锁 是并发编程中的一个重要问题。死锁是指多个线程在等待彼此持有的资源,导致无法继续执行的状态。在 Java 中,死锁通常发生在多线程程序中,尤其是在使用同步块、锁和其他并发机制时。

避免死锁有几种常见的策略和技术,下面详细介绍:

1. 资源申请顺序一致(避免循环等待)

问题: 死锁的一个重要条件是线程在不同的顺序上请求资源,导致循环等待的情况发生。

解决方法: 通过规定资源获取的顺序,使所有线程都按照相同的顺序请求锁或资源,避免产生循环等待。

示例:

class Resource1 {}
class Resource2 {}

public class DeadlockAvoidance {
    private final Resource1 r1 = new Resource1();
    private final Resource2 r2 = new Resource2();

    public void method1() {
        synchronized (r1) {
            synchronized (r2) {
                // 对资源 r1 和 r2 的操作
                System.out.println("Method 1");
            }
        }
    }

    public void method2() {
        synchronized (r1) { // 保持与 method1 相同的锁获取顺序
            synchronized (r2) {
                // 对资源 r1 和 r2 的操作
                System.out.println("Method 2");
            }
        }
    }

    public static void main(String[] args) {
        DeadlockAvoidance da = new DeadlockAvoidance();
        new Thread(da::method1).start();
        new Thread(da::method2).start();
    }
}

解释: 在 method1()method2() 中,两个线程获取资源的顺序是相同的(先 r1,再 r2)。如果每个线程都按照相同的顺序获取资源,循环等待就不会发生,从而避免了死锁。

2. 尝试锁定(使用 tryLock

问题: 当线程获取多个锁时,如果某个锁被占用,线程会一直等待,可能造成死锁。

解决方法: 使用 java.util.concurrent.locks.Lock 接口提供的 tryLock() 方法,它允许线程尝试获取锁。如果锁不可用,线程可以选择不等待,而是执行其他逻辑,避免死锁。

示例:

import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;

public class TryLockExample {
    private final Lock lock1 = new ReentrantLock();
    private final Lock lock2 = new ReentrantLock();

    public void tryLockMethod1() {
        try {
            if (lock1.tryLock() && lock2.tryLock()) {
                try {
                    // 对资源的操作
                    System.out.println("Method 1 acquired both locks");
                } finally {
                    lock2.unlock();
                    lock1.unlock();
                }
            } else {
                System.out.println("Method 1 failed to acquire locks");
            }
        } finally {
            // 释放锁的操作
        }
    }

    public void tryLockMethod2() {
        try {
            if (lock2.tryLock() && lock1.tryLock()) {
                try {
                    // 对资源的操作
                    System.out.println("Method 2 acquired both locks");
                } finally {
                    lock1.unlock();
                    lock2.unlock();
                }
            } else {
                System.out.println("Method 2 failed to acquire locks");
            }
        } finally {
            // 释放锁的操作
        }
    }

    public static void main(String[] args) {
        TryLockExample example = new TryLockExample();
        new Thread(example::tryLockMethod1).start();
        new Thread(example::tryLockMethod2).start();
    }
}

解释: 通过 tryLock() 方法,线程尝试获取锁。如果锁不可用,它可以立即返回 false,然后执行其他操作而不是一直等待,从而避免了死锁的发生。

3. 超时获取锁

问题: 在某些情况下,线程可能永远无法获取到锁,导致死锁。

解决方法: 使用带有超时的 tryLock(long timeout, TimeUnit unit) 方法来防止无限期等待锁。线程尝试在给定的时间内获取锁,如果超时,它将放弃对锁的请求,从而减少死锁的可能性。

示例:

import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
import java.util.concurrent.TimeUnit;

public class LockTimeoutExample {
    private final Lock lock1 = new ReentrantLock();
    private final Lock lock2 = new ReentrantLock();

    public void method1() {
        try {
            if (lock1.tryLock(1000, TimeUnit.MILLISECONDS)) {
                try {
                    if (lock2.tryLock(1000, TimeUnit.MILLISECONDS)) {
                        try {
                            // 对资源的操作
                            System.out.println("Method 1 acquired both locks");
                        } finally {
                            lock2.unlock();
                        }
                    } else {
                        System.out.println("Method 1 failed to acquire lock2");
                    }
                } finally {
                    lock1.unlock();
                }
            } else {
                System.out.println("Method 1 failed to acquire lock1");
            }
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }

    public void method2() {
        try {
            if (lock2.tryLock(1000, TimeUnit.MILLISECONDS)) {
                try {
                    if (lock1.tryLock(1000, TimeUnit.MILLISECONDS)) {
                        try {
                            // 对资源的操作
                            System.out.println("Method 2 acquired both locks");
                        } finally {
                            lock1.unlock();
                        }
                    } else {
                        System.out.println("Method 2 failed to acquire lock1");
                    }
                } finally {
                    lock2.unlock();
                }
            } else {
                System.out.println("Method 2 failed to acquire lock2");
            }
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }

    public static void main(String[] args) {
        LockTimeoutExample example = new LockTimeoutExample();
        new Thread(example::method1).start();
        new Thread(example::method2).start();
    }
}

解释: 如果线程无法在指定的超时时间内获取到锁,它将放弃锁的请求,避免死锁的产生。

4. 减少锁的使用范围

问题: 死锁通常发生在锁持有的时间过长或锁的范围过大时。

解决方法: 只在真正需要保护共享资源时获取锁,尽量减少锁的使用范围(即缩小同步块的范围),从而减少产生死锁的概率。

示例:

public class NarrowLockScope {
    private final Object lock = new Object();

    public void doSomething() {
        // 在非关键代码区不使用锁
        System.out.println("Doing something outside lock");

        synchronized (lock) {
            // 仅在真正需要同步时使用锁
            System.out.println("Doing something with lock");
        }
    }
}

解释: 锁只用于保护共享资源的关键部分,减少了持有锁的时间,降低了发生死锁的可能性。

5. 避免嵌套锁

问题: 嵌套锁的使用容易导致死锁。即一个线程在持有锁 A 的同时,试图去获取锁 B,另一线程在持有锁 B 时试图获取锁 A,这可能导致死锁。

解决方法: 避免嵌套锁,尽量保持获取单个锁的原则。

示例:

public class SingleLockExample {
    private final Object lock = new Object();

    public void doSomething() {
        synchronized (lock) {
            // 执行需要同步的操作
            System.out.println("Doing something");
        }
    }
}

解释: 使用一个锁,避免多个线程同时竞争多个锁,避免了嵌套锁带来的死锁问题。

6. 使用高级并发工具

问题: 手动管理锁的使用容易导致编程复杂性增加,且容易引发死锁问题。

解决方法: 使用 Java 提供的高级并发工具类,如 java.util.concurrent 包中的 SemaphoreCountDownLatchCyclicBarrierConcurrentHashMap 等来避免死锁。这些工具类通常经过良好的设计和优化,可以帮助简化并发代码,降低死锁风险。

示例:

Semaphore semaphore = new Semaphore(1);

public void doSomething() {
    try {
        semaphore.acquire(); // 获取信号量
        // 执行需要同步的操作
        System.out.println("Doing something with semaphore");
    } catch (InterruptedException e) {
        e.printStackTrace();
    } finally {
        semaphore.release(); // 释放信号量
    }
}

解释: 使用 Semaphore 可以有效管理资源的并发访问,避免传统锁机制引发的死锁问题。

总结

为了避免死锁,可以采取以下策略:

  • 确保资源获取的顺序一致,避免循环等待。
  • 使用 tryLock() 或设置

标签:Java,System,并发,死锁,println,new,public,out
From: https://www.cnblogs.com/DCFV/p/18438588

相关文章

  • [Java基础]PriorityQueue
    优先级队列数据结构是堆一.PriorityQueuePriorityQueue简介继承关系PriorityQueue示例二.Comparable比较器Compare接口三.Comparator比较器Comparator接口四.底层原理一.PriorityQueuePriorityQueue简介PriorityQueue,即优先级队列。优先级队列可以保证......
  • Java线程池内容记录
    线程池实现了对线程的复用,统一管理和维护线程,减少没有必要的开销。为什么要用线程池?为了提高效率,需要将一些业务采用多线程的方式去执行。几乎所有需要异步或并发执行任务的程序都可以使用线程池。线程池的概念和连接池是类似的。在Java集合中存储大量的线程对象,每次执行异......
  • 个位数统计Java
    1//给定一个k位整数1(0,,,d​k−1​​>0),请编写程序统计每种不同的个位数字出现的次数。例如:给定0,则有2个0,3个1,和1个3。2//输入格式3//每个输入包含1个测试用例,即一个不超过1000位的正整数。4//输出格式:5//对NN中每一种不同的个位数字,以D:M......
  • Java数据类型与运算符
    前言Java是一种广泛使用的编程语言,它以其“一次编写,到处运行”(WriteOnce,RunAnywhere,简称WORA)的理念而闻名。Java的学习将伴随着该文章展开!!一.数据类型Java的数据类型大体与C语音相类似,又有些许不同,且听我道来。基本数据类型分为整型,字符型,浮点型以及布尔类型! 1.1......
  • 华为OD机试2024年E卷-转骰子[200分]( Java | Python3 | C++ | C语言 | JsNode | Go )实
    题目描述骰子是一个立方体,每个面一个数字,初始为左1,右2,前3(观察者方向),后4,上5,下6,用123456表示这个状态,放置在平面上,可以向左翻转(用L表示向左翻转1次),可以向右翻转(用R表示向右翻转1次),可以向前翻转(用F表示向前翻转1次),可以向后翻转(用B表示向后翻转1次),可以逆时针旋转(......
  • 华为OD机试2024年E卷-矩阵匹配[200分]( Java | Python3 | C++ | C语言 | JsNode | Go )
    题目描述从一个N*M(N≤M)的矩阵中选出N个数,任意两个数字不能在同一行或同一列,求选出来的N个数中第K大的数字的最小值是多少。输入描述输入矩阵要求:1≤K≤N≤M≤150输入格式:NMKN*M矩阵输出描述N*M的矩阵中可以选出M!/N!种组合数组,每个组合......
  • 计算机毕业设计 智能旅游推荐平台的设计与实现 Java实战项目 附源码+文档+视频讲解
    博主介绍:✌从事软件开发10年之余,专注于Java技术领域、Python人工智能及数据挖掘、小程序项目开发和Android项目开发等。CSDN、掘金、华为云、InfoQ、阿里云等平台优质作者✌......
  • javaweb学习4
    今天主要学习了获取数据库连接的操作和mavenmaven导入mysql和druidjar包具体的jar坐标可以去这个网站找https://mvnrepository.com/<dependencies><dependency><groupId>mysql</groupId><artifactId>mysql-connector-java</artifactId......
  • C++多线程与并发类面试题
    题目来源:https://subingwen.cn/cpp/thread/https://mp.weixin.qq.com/s?__biz=Mzg4NDQ0OTI4Ng==&mid=2247489580&idx=1&sn=b9ac83040601230ff897f3394e956cea&chksm=cfb95145f8ced8536d5dcfa7d3165e3a51f5cb40e52f699745df0d8f71e4f7591674cd5cf156&token=......
  • java字符串连接和运算符优先级
    源代码:publicclassEnumTest{publicstaticvoidmain(String[]args){intx=100;inty=200;System.out.println("x+y="+y+x+y);System.out.println(x+y+"=x+y");}}程序输出:x+y=200100200300=x......