【Java并发】JAVA并发编程实战-读书笔记16

Java应用程序不能从死锁中恢复,所以确保你的设计能够避免死锁出现的先决条件是非常有价值的。

public class LeftRightDeadLock(){

  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(right){

        doSomethingElse();

      }

    }

  }

}

上面的程序很容易发生死锁。

如果所有线程以通用的固定顺序获得锁就不会出现顺序死锁的问题。

public void transferMoney(Account fromAccount,Account toAccount

  DollarAmount amount) throws InsufficientFundsException{

    synchronized(fromAccount){

      synchronized(toAccount){

        if(fromAccount.getBalance().compareTo(amount)<0){

       throw new InsufficientFundsException();

      }else{

        fromAccount.debit(amount);

        toAccount.credit(amount);

      }

    }

  }

}

上面的程序易导致死锁,原因:如果一个xy转账和yx转账同时发生,就会出现获得锁,等待对方锁的状态。

private static final Object tieLock=new Object();

public void transferMoney(final Account fromAccount,final Account toAccount,final DollarAmount amount)throws InsufficientFundsException{

  class Helper{

    public void transfer()throws InsufficientFundsException{

      if(fromAccount.getBalance().compareTo(amount)>0){

        throw new InsufficientFundesException();

      }else{

        fromAccount.debit(amount);

        toAccount.credit(amount);

      }

    }

  }

  int fromHash=System.identityHashCode(fromAcct);

  int toHash=System.indentityHashCode(toAcct);

  if(fromHash<toHash){

    synchronized(fromAcct){

      synchronized(toAcct){

        new Helper().transfer();

      }

    }

  }else if(fromHash>toHash){

    synchronized(toAcct){

      synchronized(fromAcct){

        new Helper.transfer();

      }

    }

  }else{

    synchronized(tieLock){

      synchronized(fromAcct){

        synchronized(toAcct){

          new Helper.transfer();

        }

      }

    }

  }

}

两个对象 hashCode 可能会有相同的数值。但是概率较低。

另外经常出现哈希冲突,那么这个技术可能会成为并发性的瓶颈。

public class DemonstrateDeadLock{

  private static final int NUM_THREADS=20;

  private static final int NUM_ACCOUNTS=5;

  private static final int NUM_ITERATIONS=1000000;

  public static void main(String[] args){

    final Random rnd=new Random();

    final Account[] accounts=new Account[NUM_ACCOUNTS];

    for(int i=0;i<accounts.length;i++){

      accounts[i]=new Account();

    }

    class TransferThread extends Thread{

      public void run(){

        for(int i=0;i<NUM_ITERATIONS;i++){

          int fromAcct=rnd.nextInt(NUM_ACCOUNTS);

          int toAcct=rnd.nextInt(NUM_ACCOUNT);

          DollarAmount amount=new DollarAmount(rnd.nextInt(1000));

          transferMoney(accounts[fromAcct],

          accounts[toAcct],amount);

        }

      }

    }

    for(int i=0;i<NUM_THREADS;i++){

      new TransferThread().start();

    }

  }

}

上面的例子在大多数系统下都会很快发生死锁。

class Taxi{

  private Point location,destination;

  private final Dispatcher dispatcher;

  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)){

      dispatche.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;

  }

}

上面的例子会造成死锁。setLocatioin 方法和 getImage 方法存在被两个线程以不同的顺序占有的可能。

在持有一个锁后,调用一个外部方法是很危险的。

class Taxi{

  private Point location,destination;

  private final Dispatcher dispatcher;

  public synchronized Point getLocation(){

    return location;

  }

  public synchronized void setLocation(Point location){

    booelan reachedDestination;

    synchronized(this){

      this.location=location;

      reachedDestination=location.equals(destination);

    }

    if(reachedDestination){

      dispatcher.notifyAvailable(this);

    }

  }

}

class Dispatcher{

  private final Set<Taxi> taxis;

  private final Set<Taxi> availableTaxis;

  public synchronized void notifyAvailable(Taxi taxi){

    availableTaxis.add(taxi);

  }

  public Image getImage(){

    Set<Taxi> copy;

    synchronized(this){

      copy=new HashSet<Taxi>(taxis);

    }

    Image image=new Image();

    for(Taxi t:copy){

      image.drawMarker(t.getLocation());

    }

    return image;

  }

}

持有定时锁可以一定程度的避免死锁,但是只有同时获得两个锁的时候才有效,如果多个锁是在嵌套的方法中被请求的,你无法仅仅释放外层的锁,尽管你知道自己已经持有锁。

活锁是线程中活跃度失败的另一种形式,尽管没有被阻塞,线程仍不能继续,因为他不断重试相同的操作,总是失败。活锁通常发生在消息处理应用中,将处理失败的消息回退回队列重新处理,这种形式的活锁通常来源于过度的错误恢复代码,误将不可修复的错误当做是可修复的错误。

活锁同样可以发生在多个线程的协作间,为了彼此间响应而修改了状态,使得没有一个线程能够继续前进。好比两个人在半路相遇,由于互相让路导致他们在另一条路上相遇,一直避让下去。

解决活锁的一种方案就是对重试机制引入一些随机性。比如以太网上两个基站间数据包冲突,如果都非常精确的休息一秒后重发会再次冲突,这是可以随机等待,然后重新发送。通过随机等待和撤回重试能够相当有效的避免活锁的发生。

可伸缩性指的是:当增加计算资源的时候,吞吐量和生产量能够相应地得以改进。

如果F是必须串行执行的比重,那么Amdahl定律告诉我们,在一个N处理器的机器中。

speedup<=1/(F+(1-F)/N)

当N趋近于无限大时,speedup最大值无限接近于1/F

所有的并发程序都有一些串行源。

有两个原因影响着锁的竞争性:锁被请求的频率、每次持有该锁的时间。如果这两者的乘积足够小,那么大多数请求锁的尝试都是非竞争的,这样竞争性的锁将不会成为可伸缩性的障碍。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值