学习笔记

Java并发编程实战-9活跃性、性能与测试_避免活跃性危险
Publish: 2018/7/23   

死锁

锁顺序死锁

A->锁住Left->尝试锁住Right->永久等待

B—>锁住Right->尝试锁住Left->永久等待

    //注意:容易发生死锁
    public class LeftRightDeadLock{
        private final Object left=new Object();
        private final Object right=new Object();

        public void leftRight(){
            synchronized(left){
                doSomething();
            }  
        }

        public void rightLeft(){
            synchronized(right){
                doSomething();
            }  
        }
    }
在LeftRightDeadLock中发生死锁的原因是:两个线程试图以不同的顺序来获得相同的锁。 *如果所有线程以固定的顺序来获得锁,那么在程序中就不会出现锁顺序死锁问题。* ### 动态的锁顺序死锁 锁的顺序取决于传递的参数顺序
A:transferMoney(myAccount,yourAccount,10);
B:transferMoney(yourAccount,myAccount,20);
    //注意:容易发生死锁!
    public void transferMoney(Account fromAccout,
                              Account toAccout,
                              DollarAmount amount)
            throws InsufficientFundsException{
        synchronized(fromAccount){
            synchronized(toAccount){
                if(fromAccount.getBalance().compareTo(amount)<0) throw="" new="" insufficientfundsexception();="" else{="" fromaccount.debit(amount);="" toaccount.credit(amount);="" }="" <="" pre="">

可以使用System.identityHashCode方法,该方法将返回由Object.hashCode返回的值。散列冲突的频率非常低,为了避免这种情况可以使用”加时赛”锁,在获得两个Account锁之前,首先获得这个”加时赛”锁。或者使用Account中唯一不变,且具备可比性的键值,如账号,代替”加时赛”锁。

    private static final Object tieLock=new Object;

    public void transferMoney(Account fromAccout,
                              Account toAccout,
                              DollarAmount amount)
            throws InsufficientFundsException{
        class Helper{
            public void transfer() throws inesufficientFundsException{
                if(fromAcct.getBalance().compareTo(amount)<0) throw="" new="" insufficientfundsexception();="" else{="" fromacct.debit(amount);="" toacct.credit(amount);="" }="" int="" fromhash="System.identityHashCode(fromAcct);" tohash="System.identityHashCode(toAcct);" if(fromhash="" <="" tohash){="" synchronized(fromacct){="" synchronized(toacct){="" helper().transfer();="" }else=""> toHash){
            synchronized(toAcct){
                synchronized(fromAcct){
                    new Helper().transfer();
                }
            }
        }else{
            synchronized(tieLock){
                synchronized(fromAcct){
                    synchronized(toAcct){
                        new Helper().transfer();
                    }
                }
            }
        }
    }    

在协作对象之间发生的死锁

如果在持有锁时调用某个外部方法,那么将出现活跃性问题。在这个外部方法中可能会获取其他锁(这可能会产生死锁),或者阻塞时间过长,导致其他线程无法及时获得当前被持有的锁。

开放调用

如果在调用某个方法时不需要持有锁,那么这种调用被称为开放调用。

    public synchronized void setLocation(Point location){
        this.location=location;
        if(location.equals(destination))
            //可能需要获取其他锁
            dispatcher.notifyAvailable(this);
    }
    //修改为开放调用
    public synchronized void setLocation(Point location){
        boolean reachedDestination;
        synchronized(this){
            this.location=location;
            reachedDestination = location.equals(destination);
        }
        if(reachedDestination)
            dispatcher.notifyAvailable(this);
    } 

在程序中应尽量使用开放调用。与那些在持有锁时调用外部方法的程序相比,更易于对依赖于开放调用的程序进行死锁分析。

资源死锁

  • 一个任务需要谅解两个数据库,并且在请求这两个资源时不会始终遵循相同的顺序。

    线程A持有与数据库D1的连接,并等待与数据库D2的连接,

    线程B持有与数据可D2的连接,并等待与数据库D·的连接,将产生资源死锁。

  • 线程饥饿死锁:如果某些任务需要等待其他任务的结果,那么这些任务往往是产生线程饥饿死锁的主要来源,有界线程池/资源池与相互依赖的任务不能一起使用。

死锁的避免与诊断

  • 尽量减少潜在的加锁交互数量,将获取锁时需要遵循的协议写入正式文档并始终遵循这些协议。
  • 两阶段策咯:首先,超出在什么地方将获取多个锁(使这个集合尽量小),然后对所有这些实例进行全局分析,从而确保它们在整个程序中获取锁的顺序都保持一致。

支持定时的锁

显示使用Lock类中的定时tryLock功能来代替内置锁机制。显示锁可以指定一个超时时限,在等待超过该时间后tryLock会返回一个失败信息。

通过线程转存储信息来分析死锁

JVM通过线程转存储(Thread Dump)来帮助识别死锁的发生。内置锁与获得它们所在的线程栈帧是相关联的,而显示的Lock只与获得它的线程想关联。

其他活跃性危险

饥饿

引发饥饿的最常见资源就是CPU时钟周期。如果在Java应用程序中对线程的优先级使用不当,或者在持有锁时执行一些无法结束的结构,那么可能导致饥饿。

要避免使用线程优先级,因为这会增加平台依赖性,并可能导致活跃性问题。在大多数并发应用程序中,都可以使用默认的线程优先级。

糟糕的响应性

如果某个线程长时间占有一个锁(或许正在对一个大容器进行迭代,并且对每个元素进行密集型的处理),而其他想要访问这个容器的线程就必须等待很长时间。

活锁

当多个相互协作的线程都对彼此进行相应从而修改各自的状态,并使得任何一个线程都无法继续执行时,就发生了活锁。

要解决这种活锁问题,需要在重试机制中引入随机性。在并发应用程序中,通过等待随机长度的时间和回退可以有效地避免活锁的发生。



← Java并发编程实战-10活跃性、性能与测试_性能与可伸缩性 C++PrimerPlus内存模型和名称空间 →

Powered by Hexo, Theme designs by @hpcslag.
Style-Framework Tocas-UI designs by @yamioldmel