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

来源:互联网 发布:注册中文域名有价值吗 编辑:程序博客网 时间:2024/06/04 00:51

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
原创粉丝点击