《Java併發編程實戰》筆記4——避免活躍性危險

1、死鎖

(1)鎖順序死鎖

例子:

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

    public void leftRight() {
        synchronized (left) {
        	System.out.println(Thread.currentThread().getName()+" 拿到了left鎖");
        	try {
        		//爲了讓另一個線程有執行機會,拿到第一個鎖需要休眠一下
				TimeUnit.SECONDS.sleep(1);
			} catch (InterruptedException e) {
				e.printStackTrace();
			}
            synchronized (right) {
            	System.out.println(Thread.currentThread().getName()+" 拿到了right鎖");
                doSomething();
            }
        }
    }

    public void rightLeft() {
        synchronized (right) {
        	System.out.println(Thread.currentThread().getName()+" 拿到了right鎖");
        	try {
				TimeUnit.SECONDS.sleep(1);
			} catch (InterruptedException e) {
				e.printStackTrace();
			}
            synchronized (left) {
            	System.out.println(Thread.currentThread().getName()+" 拿到了left鎖");
                doSomethingElse();
            }
        }
    }

    void doSomething() {
    }

    void doSomethingElse() {
    }
    
    public static void main(String[] args) {
    	LeftRightDeadlock leftRight = new LeftRightDeadlock();
		Thread t1 = new Thread(new Runnable() {
			@Override
			public void run() {
				leftRight.leftRight();
			}
		},"bam");
		t1.start();
		Thread t2 = new Thread(new Runnable() {
			@Override
			public void run() {
				leftRight.rightLeft();
			}
		},"boo");
		t2.start();
	}
}

 運行結果:

此處是bam先執行,如下圖所示:

 

在該案例中發生死鎖的原因是:兩個線程試圖以不同的順序來獲得相同的鎖,如果按照相同的順序來請求鎖,那麼就不會出現循環的加鎖依賴,也就不會產生死鎖。

(2)動態的鎖順序死鎖

①動態的鎖順序死鎖

public class DynamicOrderDeadlock {
	// Warning: deadlock-prone!
	public static void transferMoney(Account fromAccount, Account toAccount, DollarAmount amount)
			throws InsufficientFundsException {
		synchronized (fromAccount) {
			System.out.println(Thread.currentThread().getName()+"拿到了"+fromAccount.getAcctNo());
			try {
				TimeUnit.SECONDS.sleep(1);
			} catch (InterruptedException e) {
				e.printStackTrace();
			}
			synchronized (toAccount) {
				System.out.println(Thread.currentThread().getName()+"拿到了"+toAccount.getAcctNo()+"SUCCESS");
				if (fromAccount.getBalance().compareTo(amount) < 0)
					throw new InsufficientFundsException();
				else {
					fromAccount.debit(amount);
					toAccount.credit(amount);
				}
			}
		}
	}

	static class DollarAmount implements Comparable<DollarAmount> {
		// Needs implementation
		int amount;

		public DollarAmount(int amount) {
			this.amount = amount;
		}

		public DollarAmount add(DollarAmount d) {
			amount += d.amount;
			return this;
		}

		public DollarAmount subtract(DollarAmount d) {
			amount -= d.amount;
			return this;
		}

		public int compareTo(DollarAmount dollarAmount) {
			return this.amount - dollarAmount.amount;
		}
	}

	static class Account {
		private DollarAmount balance;
		private final int acctNo;
		private static final AtomicInteger sequence = new AtomicInteger();

		public Account() {
			acctNo = sequence.incrementAndGet();
		}

		void debit(DollarAmount d) {
			balance = balance.subtract(d);
		}

		void credit(DollarAmount d) {
			balance = balance.add(d);
		}

		DollarAmount getBalance() {
			return balance;
		}

		int getAcctNo() {
			return acctNo;
		}
	}

	static class InsufficientFundsException extends Exception {
	}

	public static void main(String[] args) throws InsufficientFundsException {
		DollarAmount d1 = new DollarAmount(10);
		DollarAmount d2 = new DollarAmount(10);
		DollarAmount paid = new DollarAmount(5);
		Account a1 = new Account();
		a1.balance = d1;
		Account a2 = new Account();
		a2.balance = d2;
		new Thread(new Runnable() {
			@Override
			public void run() {
				try {
					transferMoney(a1, a2, paid);
				} catch (InsufficientFundsException e) {
					e.printStackTrace();
				}
			}
		},"a1Toa2").start();
		new Thread(new Runnable() {
			@Override
			public void run() {
				try {
					transferMoney(a2, a1, paid);
				} catch (InsufficientFundsException e) {
					e.printStackTrace();
				}
			}
		},"a2Toa1").start();

		System.out.println("a1:" + a1.getBalance().amount + "====a2:" + a2.getBalance().amount);
	}
}

運行結果:

改進:通過鎖順序來避免死鎖

public static void transferMoney(Account fromAccount, Account toAccount, DollarAmount amount)
			throws InsufficientFundsException {
		
		class Helper{
			public void transfer() throws InsufficientFundsException {
				if(fromAccount.getBalance().compareTo(amount)< 0)
					throw new InsufficientFundsException();
				else {
					fromAccount.debit(amount);
					toAccount.credit(amount);
				}
			}
		}
		int fromHash = System.identityHashCode(fromAccount);
		int toHash = System.identityHashCode(toAccount);
		if(fromHash<toHash) {
			synchronized (fromAccount) {
				synchronized (toAccount) {
					new Helper().transfer();
				}
			}
		}else if(fromHash>toHash) {
			synchronized (toAccount) {
				synchronized (fromAccount) {
					new Helper().transfer();
				}
			}
		}else {
			synchronized (tieLock) {
				synchronized (fromAccount) {
					synchronized (toAccount) {
						new Helper().transfer();
					}
				}
			}
		}
}

②在典型條件下會發生死鎖的循環

 

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();
        	DollarAmount d1 = new DollarAmount(10);
        	accounts[i].balance =d1;
        }
            
        

        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_ACCOUNTS);
                    DollarAmount amount = new DollarAmount(rnd.nextInt(1000));
                    try {
                        DynamicOrderDeadlock.transferMoney(accounts[fromAcct], accounts[toAcct], amount);
                    } catch (DynamicOrderDeadlock.InsufficientFundsException ignored) {
                    }
                }
            }
        }
        for (int i = 0; i < NUM_THREADS; i++)
            new TransferThread().start();
    }
}

(3)在協作對象之間發生的死鎖

例子:

public class CooperatingDeadlock {
	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) {//線程1:獲取Taxi上的鎖
			this.location = location;
			if (location.equals(destination))
				dispatcher.notifyAvailable(this);//線程1:獲取Dispatcher的鎖
		}
		public synchronized Point getDestination() {
			return destination;
		}
		public synchronized void setDestination(Point destination) {
			this.destination = destination;
		}
	}

	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() {//線程2:獲取Dispatcher的鎖
			Image image = new Image();
			for (Taxi t : taxis)
				image.drawMarker(t.getLocation());//線程2:獲取每一個Taxi的鎖
			return image;
		}
	}

	class Image {
		public void drawMarker(Point p) {
			System.out.println();
		}
	}
}

(4)開放調用

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

       ...
        public synchronized Point getLocation() {
            return location;
        }

        public void setLocation(Point location) {
            boolean 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;
        }
    }

 

 (5)資源死鎖

2、如何避免死鎖

(1)使用支持定時的鎖

(2)使用Thread Dump來分析死鎖(JDK自帶命令工具jps、jstack等)

3、活鎖

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章