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);
}
}
}
}
上面的程序易导致死锁,原因:如果一个x到y转账和y到x转账同时发生,就会出现获得锁,等待对方锁的状态。
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。
所有的并发程序都有一些串行源。
有两个原因影响着锁的竞争性:锁被请求的频率、每次持有该锁的时间。如果这两者的乘积足够小,那么大多数请求锁的尝试都是非竞争的,这样竞争性的锁将不会成为可伸缩性的障碍。