`
wbj0110
  • 浏览: 1603875 次
  • 性别: Icon_minigender_1
  • 来自: 上海
文章分类
社区版块
存档分类
最新评论

死锁与活跃度

    博客分类:
  • Java
阅读更多
  • 锁顺序死锁(lock-ordering deadlock):多个线程试图通过不同的顺序获得多个相同的资源,则发生的循环锁依赖现象。
  • 动态的锁顺序死锁(Dynamic Lock Order Deadlocks):多个线程通过传递不同的锁造成的锁顺序死锁问题。
  • 资源死锁(Resource Deadlocks):线程间相互等待对方持有的锁,并且谁都不会释放自己持有的锁发生的死锁。也就是说当现场持有和等待的目标成为资源,就有可能发生此死锁。这和锁顺序死锁不一样的地方是,竞争的资源之间并没有严格先后顺序,仅仅是相互依赖而已。
 

锁顺序死锁

最经典的锁顺序死锁就是LeftRightDeadLock.


public class LeftRightDeadLock {

    final Object left = new Object();
    final Object right = new Object();

    public void doLeftRight() {
        synchronized (left) {
            synchronized (right) {
                execute1();
            }
        }
    }

    public void doRightLeft() {
        synchronized (right) {
            synchronized (left) {
                execute2();
            }
        }
    }

    private void execute2() {
    }

    private void execute1() {
    }
}


这个例子很简单,当两个线程分别获取到left和right锁时,互相等待对方释放其对应的锁,很显然双方都陷入了绝境。

 

动态的锁顺序死锁

与锁顺序死锁不同的是动态的锁顺序死锁只是将静态的锁变成了动态锁。 一个比较生动的例子是这样的。

 

public void transferMoney(Account fromAccount,//
        Account toAccount,//
        int amount
        ) {
    synchronized (fromAccount) {
        synchronized (toAccount) {
            fromAccount.decr(amount);
            toAccount.add(amount);
        }
    }
}


当我们银行转账的时候,我们期望锁住双方的账户,这样保证是原子操作。 看起来很合理,可是如果双方同时在进行转账操作,那么就有可能发生死锁的可能性。

 

很显然,动态的锁顺序死锁的解决方案应该看起来和锁顺序死锁解决方案差不多。 但是一个比较特殊的解决方式是纠正这种顺序。 例如可以调整成这样:

Object lock = new Object();

public void transferMoney(Account fromAccount,//
        Account toAccount,//
        int amount
        ) {
    int order = fromAccount.name().compareTo(toAccount.name());
    Object lockFirst = order>0?toAccount:fromAccount;
    Object lockSecond = order>0?fromAccount:toAccount;
    if(order==0){
        synchronized(lock){
            synchronized(lockFirst){
                synchronized(lockSecond){
                    //do work
                }
            }
        }

    }else{
        synchronized(lockFirst){
            synchronized(lockSecond){
                //do work
            }
        }
    }
}

 

这个挺有意思的。比较两个账户的顺序,保证此两个账户之间的传递顺序总是按照某一种锁的顺序进行的, 即使多个线程同时发生,也会遵循一次操作完释放完锁才进行下一次操作的顺序,从而可以避免死锁的发生。

 

资源死锁

资源死锁比较容易理解,就是需要的资源远远大于已有的资源,这样就有可能线程间的资源竞争从而发生死锁。 一个简单的场景是,应用同时从两个连接池中获取资源,两个线程都在等待对方释放连接池的资源以便能够同时获取 到所需要的资源,从而发生死锁。

资源死锁除了这种资源之间的直接依赖死锁外,还有一种叫线程饥饿死锁(thread-starvation deadlock)。 严格意义上讲,这种死锁更像是活跃度问题。例如提交到线程池中的任务由于总是不能够抢到线程从而一直不被执行, 造成任务的“假死”状况。

除了上述几种问题外,还有协作对象间的死锁以及开发调用的问题。这个描述起来会比较困难,也不容易看出死锁来。

 

避免和解决死锁

通常发生死锁后程序难以自恢复。但也不是不能避免的。 有一些技巧和原则是可以降低死锁可能性的。

最简单的原则是尽可能的减少锁的范围。锁的范围越小,那么竞争的可能性也越小。 尽快释放锁也有助于避开锁顺序。如果一个线程每次最多只能够获取一个锁,那么就不会产生锁顺序死锁。尽管应用中比较困难,但是减少锁的边界有助于分析程序的设计和简化流程。 减少锁之间的依赖以及遵守获取锁的顺序是避免锁顺序死锁的有效途径。

另外尽可能的使用定时的锁有助于程序从死锁中自恢复。 例如对于上述顺序锁死锁中,使用定时锁很容易解决此问题。

 

public void doLeftRight() throws Exception {
    boolean over = false;
    while (!over) {
        if (left.tryLock(1, TimeUnit.SECONDS)) {
            try {
                if (right.tryLock(1, TimeUnit.SECONDS)) {
                    try {
                        execute1();
                    } finally {
                        right.unlock();
                        over = true;
                    }
                }
            } finally {
                left.unlock();
            }
        }
    }
}

public void doRightLeft() throws Exception {
    boolean over = false;
    while (!over) {
        if (right.tryLock(1, TimeUnit.SECONDS)) {
            try {
                if (left.tryLock(1, TimeUnit.SECONDS)) {
                    try {
                        execute2();
                    } finally {
                        left.unlock();
                        over = true;
                    }
                }
            } finally {
                right.unlock();
            }
        }
    }
}


看起来代码会比较复杂,但是这是避免死锁的有效方式。

 

 

活跃度

对于多线程来说,死锁是非常严重的系统问题,必须修正。除了死锁,遇到很多的就是活跃度问题了。 活跃度问题主要包括:饥饿,丢失信号,和活锁等。

 

饥饿

饥饿是指线程需要访问的资源被永久拒绝,以至于不能在继续进行。 比如说:某个权重比较低的线程可能一直不能够抢到CPU周期,从而一直不能够被执行。

也有一些场景是比较容易理解的。对于一个固定大小的连接池中,如果连接一直被用完,那么过多的任务可能由于一直无法抢占到连接从而不能够被执行。这也是饥饿的一种表现。

对于饥饿而言,就需要平衡资源的竞争,例如线程的优先级,任务的权重,执行的周期等等。总之,当空闲的资源较多的情况下,发生饥饿的可能性就越小。

 

弱响应性

弱响应是指,线程最终能够得到有效的执行,只是等待的响应时间较长。 最常见的莫过于GUI的“假死”了。很多时候GUI的响应只是为了等待后台数据的处理,如果线程协调不好,很有可能就会发生“失去响应”的现象。

另外,和饥饿很类似的情况。如果一个线程长时间独占一个锁,那么其它需要此锁的线程很有可能就会被迫等待。

 

活锁

活锁(Livelock)是指线程虽然没有被阻塞,但是由于某种条件不满足,一直尝试重试,却终是失败。

考虑一个场景,我们从队列中拿出一个任务来执行,如果任务执行失败,那么将任务重新加入队列,继续执行。假如任务总是执行失败,或者某种依赖的条件总是不满足,那么线程一直在繁忙却没有任何结果。

错误的循环引用和判断也有可能导致活锁。当某些条件总是不能满足的时候,可能陷入死循环的境地。

线程间的协同也有可能导致活锁。例如如果两个线程发生了某些条件的碰撞后重新执行,那么如果再次尝试后依然发生了碰撞,长此下去就有可能发生活锁。

解决活锁的一种方案是对重试机制引入一些随机性。例如如果检测到冲突,那么就暂停随机的一定时间进行重试。这回大大减少碰撞的可能性。

另外为了避免可能的死锁,适当加入一定的重试次数也是有效的解决办法。尽管这在业务上会引起一些复杂的逻辑处理。

 

http://www.blogjava.net/xylz/archive/2011/12/29/365149.html

分享到:
评论

相关推荐

    IBM Thread and Monitor Dump Analyzer for Java

    4. **线程活跃度**:工具提供线程活跃度统计,帮助识别那些长期运行或者阻塞的线程,这可能是性能下降的原因。通过这些信息,开发者可以优化代码,减少不必要的等待和阻塞。 5. **内存占用分析**:除了线程,该工具...

    1.0-GS源码-完整版

    3. **多线程与并发**:学习如何处理并发请求,保证服务器的高效运行,避免死锁和资源竞争。 4. **数据库设计与操作**:研究源码中与数据库交互的部分,了解SQL语句的编写和优化,以及事务处理。 5. **游戏逻辑实现**...

    《操作系统原理》一课一文

    课程的教学方式受到了学生的认可,但也提出了改进的建议,如增加课堂互动,采用反转课堂等教学模式,以提升课堂活跃度和学习效果。 总结来说,《操作系统原理》课程不仅提供了丰富的理论知识,还强调了实践操作,...

    mysql面试题及答案

    - 垂直分库分表:根据业务划分表,或按字段活跃度拆分表。 - 常用中间件:sharding-jdbc、Mycat、TDDL、Oceanus、Vitess、Atlas等。 - 面临的问题:事务管理、跨节点Join、统计计算、数据迁移、容量规划、ID生成...

    java oracle并发官方教程

    活跃度问题通常表现为死锁、饥饿和活锁。其中,死锁是指两个或多个线程因争夺资源而无限等待的情况;饥饿是指线程因无法获得所需资源而无法执行;活锁指的是线程不断重复执行某些操作,而无法向前推进。 ### ...

    Thread Dump Analyzer - tda-bin-2.3.3

    5. **线程活跃度统计**:TDA 还可以统计线程的活跃程度,比如执行时间最长的线程、最频繁执行的代码段等,这些数据对于优化程序性能很有帮助。 6. **过滤和搜索**:用户可以使用关键词过滤和搜索线程信息,快速定位...

    mysql面试题大全.docx

    - 垂直分库和分表根据业务逻辑划分表,或者根据字段活跃度将字段拆分到主表和扩展表。 - 常见的分库分表中间件有Sharding-JDBC、Mycat、TDDL、Oceanus和Vitess等。 - 分库分表会面临事务处理、跨节点查询、统计...

    MySQL 70 道面试题及答案.docx

    4. 垂直分表:按字段活跃度拆分 **分库分表中间件**:sharding-jdbc、Mycat、TDDL、Oceanus、Vitess、Atlas等 **分库分表问题:** 1. 分布式事务处理 2. 跨节点Join 3. 跨节点聚合操作 4. 数据迁移和容量规划 5. ...

    操作系统试卷

    试卷中涉及了死锁预防策略之一——资源序列分配(assign resource sequence),这是一种防止死锁的方法,通过预定义的资源请求顺序来避免循环等待条件的发生,从而避免死锁。 ### CPU模式 操作系统和应用程序在...

    Java并发编程(学习笔记).xmind

    活跃度(Liveness)、性能、测试 避免活跃性危险 死锁 锁顺序死锁 资源死锁 动态的锁顺序死锁 开放调用 在协作对象之间发生的死锁 死锁的避免与诊断 支持定时的显示锁 通过...

    100道MySQL数据库经典面试题

    - **方案**:水平分库/分表(根据字段策略拆分)、垂直分库/分表(按业务或字段活跃性拆分)。 - **中间件**:Sharding-JDBC、Mycat、TDDL、Oceanus、Vitess、Atlas等。 - **可能遇到的问题**:事务处理、跨节点...

    MySQL 75道面试题及答案.docx

    4. 垂直分表:以字段为依据,按照字段的活跃性,将表中字段拆到不同的表(主表和扩展表)中。 常用的分库分表中间件: 1. sharding-jdbc(当当) 2. Mycat 3. CTDDL(淘宝) 4. Oceanus(58同城数据库中间件) 5. ...

    leetcode赛车-Concurrency:#JAVA#并发

    并发应用程序及时执行的能力被称为活跃度。 最常见的一种活性问题,死锁,以及另外两种活性问题,饥饿和活锁。 僵局 死锁描述了两个或多个线程被永远阻塞、互相等待的情况。 饥饿 饥饿描述了线程无法定期访问共享...

    Java并发编程实践

    - **避免活跃度问题**:如无限循环和阻塞,确保程序能够正常响应。 - **避免死锁和资源争抢**:通过合理设计资源获取顺序和释放策略,防止死锁的发生。 - **正确使用并发工具**:根据具体需求选择合适的并发工具...

    100道MySql面试题

    4. 垂直分表:以字段为依据,按照字段的活跃性,将表中字段拆到不同的表(主表和扩展表)中。 常用的分库分表中间件: 1. sharding-jdbc(当当) 2. Mycat 3. TDDL(淘宝) 4. Oceanus(58 同城数据库中间件) 5. ...

Global site tag (gtag.js) - Google Analytics