java并发编程中的活跃性问题

合理的使用多线程,可以提高程序的响应能力,吞吐能力;能够提高硬件资源的利用率。但是如果对多线程不加以合理的利用:比如说对资源不合理的加锁。可能会造成很多的活跃性问题。那么会造成哪些活跃性问题呢?

 

 一.死锁

       死锁的经典问题:哲学家问题。大家应该已经耳熟能详了。五个哲学家围绕着同一张桌子吃饭,桌子上共有5根筷子,放在他们每两个人的中间。哲学家们呢,时而思考,时而拿起左右两边的筷子就餐,吃完呢,就把筷子再放回原来的位置。如果每个人都立即拿起自己左边的筷子不放,那么这五个哲学家,就饿死了。这个问题产生的原因就是每个人都拥有其他人需要的资源,并且在等待其他人释放资源,而且每个人再获得到自己需要的资源之前都不会放弃已经拥有的资源。

        怎么解决上边的问题呢?其实如果某个哲学家尝试获得两根筷子失败的时候,其中一根筷子被其他人使用的话,他放弃自己已经获得的筷子,过段时间再去尝试。就会解决上面的问题。

       

        线程A持有锁L并且同时想获取锁M,与此同时线程B持有锁M的同时想获取锁L。两个线程互不相让,最终导致死锁。这是最简单的死锁形式。其实如果多个线程存在环路的锁依赖关系都将产生死锁。java中是没有像数据库那样的死锁检测的机制的,如果发生了死锁,没有外部干预的话,发生死锁的线程都将会等待在那里。

       

       下面我们站在代码的角度看一下死锁产生的情况,通过了解这些内容,我们在工作中,也就会避免一些产生死锁的场景。

 

1.锁顺序死锁:不同的线程以不同的顺序获得两个相同的锁。

public class LeftRightLock {
    private final Object left = new Object();
    private final Object right = new Object();

    public void leftRight() {
        synchronized (left) {
            synchronized (right) {
                // doSomething
            }
        }
    }

    public void rightLeft() {
        synchronized (right) {
            synchronized (left) {
                // doSomething
            }
        }
    }
}

当一个线程调用leftRight方法的同时另一个线程正在调用rightLeft方法,那么程序就很容易因为这种循环的所依赖发生死锁。

 2.动态的锁顺序依赖:

 

public class TransferMoney {
    public void transferMoney(Account fromAccount, Account toAccount, Integer amount) {
        synchronized (fromAccount) {
            synchronized (toAccount) {
                // doSomething
            }
        }
    }
}
上诉代码看似每什么问题,都是以相同的顺序来获取锁的,按理说不应该会发生死锁问题。但是我们从方法调用的角度看,如果两个不同的线程同时调用这个方法,一个是从Account A转账到Account B;另一个是从
Account B转账到Account A(这里全局每一个账户对应一个对象),那么照样还是会出现锁顺序依赖导致的死锁问题。

 

 上面这两个问题都是因为锁顺序依赖的问题导致的死锁,那么如果想要避免因为这种情况导致的死锁问题,我们一下便想的到的方法是,我们自己去定义锁的顺序。这样不就避免了因为以不同的顺序调用两个相互关联的锁导致的死锁问题了么?

 

3.公开协作对象之间的调用引起的死锁:

 

class Taxi {
    private final Dispatcher dispatcher;
    private Point location, destination;

    public Taxi(Dispatcher dispatcher) {
        this.dispatcher = dispatcher;
    }

    public synchronized Point getLocation() {
        return location;
    }

    public synchronized void setLocation(Point location) {
        this.location = location;
        if (location.equals(destination)) {
            dispatcher.notifyAvailable(this);
        }
    }
}

class Dispatcher {
    private final Set<Taxi> taxis;
    private final Set<Taxi> availableTaxis;

    public Dispatcher() {
        taxis = new HashSet<Taxi>();
        availableTaxis = new HashSet<Taxi>();
    }

    public synchronized void notifyAvailable(Taxi taxi) {
        availableTaxis.add(taxi);
    }

    public synchronized Image getImage() {
        Image image = new Image();
        for (Taxi t : taxis) {
            image.drawMarker(t.getLocation());
        }

        return image;
    }

}

Taxi代表一个出租车对象,Dispatcher代表一个调度系统。从单个对象上来看,没有显示的锁依赖。但是我们从两个对象协作的角度来看,getImage和setLocation两个方法都涉及到同时去获得两个锁。如果一个线程持
有Dispathcer锁等待Taxi锁;另一个线程持有Taxi锁等待Dispatcher锁。那么就会造成死锁。

 4.由于对与有限资源的竞争造成的死锁:

       比如有两个资源池,池子里边存放的是不同的两种资源,并且每个池子里边只有一个资源;某两个线程同时需要得到这两种资源,这两个线程如果各自获得了其中一个资源,与此同时在等待另一个池子里边有空闲资源。那么就会造成死锁。这种情况如果资源越少越容易发生死锁。

 

如何解决避免死锁问题?

     针对死锁问题,我们可以采用以下几种方法来避免死锁的产生:

  • 限制锁的调用顺序。
  • 缩小锁的范围。
  • 使用显示锁替换内置锁。显示锁可以有更加灵活的锁的策略,比如可以指定一定时间范围获得不了锁的话,可以进行失败,进行锁失败策略处理。而不是一味的等待。

二.饥饿

      饥饿也是多线程应用中的一种常见的活跃性问题。由于线程得不到需要的资源,不能正常执行,就会造成线程饥饿。比如说,对线程的优先级设置不当,造成线程不能获得CPU周期执行导致饥饿,或者说其他线程长时间持有锁,导致其他线程长时间等待,造成的饥饿。

 

三.活锁:

      活锁问题不会导致线程阻塞,但是活锁会导致线程不能继续正常执行。比如这样一个消息系统中,从队列里边取的消息,然后执行,但是由于某种业务原因,失败了,那么把它放到队列头,然后再拿出来执行,自然还是失败的,这样线程虽然没有阻塞,但是也不能正常的处理其他的消息。

       要解决上诉问题,还需要涉及合理的重试策略。

   

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

    

   

    

 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
### 回答1: 《Java并发编程实战》是一本经典的Java并发编程指南,由Brian Goetz等人撰写。这本书针对Java多线程并发编程的实践问题给出了详细的解决方案和最佳实践。 该书的主要内容包括:线程安全性、对象的共享与发布、锁的优化、性能与可伸缩性、构建和组合对象、基础构建模块、任务执行、取消与关闭、线程池的使用、显式锁、构建自定义的同步工具等。 通过阅读这本书,读者可以了解Java的各种并发问题,并学习如何设计和实现线程安全的Java应用。该书引入了很多并发编程的常见问题,例如:竞态条件、死锁、活跃性危险等,并提供了一些模式和技术来解决这些问题。 除了基础知识之外,该书还介绍了一些高级的并发编程概念,例如:并发集合、同步类、线程池等。这些内容将帮助读者更好地理解和利用Java并发编程能力。 总的来说,《Java并发编程实战》是一本权威而实用的Java并发编程指南,适合对多线程编程有一定了解的Java开发人员阅读。通过阅读这本书,读者可以更深入地理解Java并发编程的原理与应用,提高自己的并发编程能力。 ### 回答2: 《Java并发编程实战》是由美国计算机科学家布莱恩·戈策等人合著的一本书,是学习Java并发编程的经典教材。这本书系统地介绍了Java的多线程、并发和并行编程的基本知识和应用。该书分为三个部分,分别是基础篇、高级主题篇和专题扩展篇。 在基础篇,书详细介绍了Java内存模型、线程安全性、对象的共享、发布和逸出等概念。同时,还对Java的锁、线程池、阻塞队列等常用并发工具进行了深入讲解,帮助读者理解并发编程的基本原理和机制。 高级主题篇则讨论了一些更加复杂的并发编程问题,如线程间的协作、线程间通信、并发集合类的使用和自定义的同步工具的设计等内容。该篇章通过讲解常见的并发问题和解决方案,提供了应对复杂并发场景的实践经验。 专题扩展篇主要讨论了一些与并发编程相关的主题,如并发性问题的调试与测试、程序性能调优等。这些内容能够帮助读者进一步提升对并发编程的理解和应用水平。 《Java并发编程实战》通过深入浅出的语言和大量实例,帮助读者掌握并发编程的基本概念和技术,为读者提供设计和编写高效多线程程序的实践经验。无论是Java初学者还是有一定经验的开发人员,都可以从获得丰富的知识和实用的技巧,加速自己在并发编程领域的成长。 ### 回答3: 《Java并发编程实战》是一本经典的Java多线程编程指南,被广泛认可为学习并发编程的必读之作。本书由Brian Goetz等多位并发编程领域的专家合著,内容全面且深入浅出,适合从初学者到高级开发人员阅读。 该书分为四个部分,共16章。第一部分介绍了并发编程的基础知识,包括线程安全性、对象的共享、对象组合等。第二部分讲解了如何构建可复用的并发构件,包括线程安全性、发布与初始化安全性等。第三部分深入讨论了Java并发编程常见的问题和挑战,例如活跃性、性能与可伸缩性等。第四部分则介绍了一些高级主题,如显式锁、原子变量和并发集合等。 书包含了大量的示例代码和实践案例,可以帮助读者更好地理解并发编程的概念和技术。此外,本书还提供了一些最佳实践和经验教训,帮助读者避免常见的并发编程陷阱和错误。 《Java并发编程实战》从理论到实践的结合非常好,书所介绍的内容都是经过实践验证的,具有很高的可靠性和实用性。无论是初学者还是有一定经验的开发人员,都可以从获得实际应用的知识和经验。 综上所述,如果你想系统地学习Java并发编程,了解如何编写高效、可靠的多线程代码,那么《Java并发编程实战》是一本值得推荐的书籍。它可以帮助你深入理解并发编程的原理和技术,提高自己在并发编程领域的能力和水平。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值