Java 多線程(五) 多線程的同步
為什么要引入同步機制
在多線程環境中,可能會有兩個甚至更多的線程試圖同時訪問一個有限的資源。必須對這種潛在資源沖突進行預防。
解決方法:在線程使用一個資源時為其加鎖即可。
訪問資源的第一個線程為其加上鎖以后,其他線程便不能再使用那個資源,除非被解鎖。
程序實例
用一個取錢的程序例子,來說明為什么需要引入同步。
在使用同步機制前,整體程序如下:
public class FetchMoneyTest { public static void main(String[] args) { Bank bank = new Bank(); Thread t1 = new MoneyThread(bank);// 從銀行取錢 Thread t2 = new MoneyThread(bank);// 從取款機取錢 t1.start(); t2.start(); } } class Bank { private int money = 1000; public int getMoney(int number) { if (number < 0) { return -1; } else if (number > money) { return -2; } else if (money < 0) { return -3; } else { try { Thread.sleep(1000); } catch (InterruptedException e) { e.printStackTrace(); } money -= number; System.out.println("Left Money: " + money); return number; } } } class MoneyThread extends Thread { private Bank bank; public MoneyThread(Bank bank) { this.bank = bank; } @Override public void run() { System.out.println(bank.getMoney(800)); } }
程序中定義了一個Bank類,其中包含了用戶存儲的錢(1000元),然后用兩個線程進行取錢操作,可以看到盡管Bank類中的getMoney()方法對取錢數目與存款數據進行了判斷,但是執行后,結果輸出兩個800,表明從兩個線程中都成功地取出了800元錢。
這是為什么呢?因為getMoney()方法中有一些邏輯判斷,進入最后一個else語句塊后,有一個簡短的休眠,那么在第一個線程休眠的過程中,第二個線程也成功進入了這個else語句塊(因為存款的錢還沒有取走),當兩個線程結束休眠后,不再進行邏輯判斷而是直接將錢取走,所以兩個線程都取到了800元錢,此時money為負600。
需要注意這里並不能確定哪一個線程是第一個線程,哪一個線程是第二個線程,先后順序是不定的。
在getMoney()方法中加入打印語句輸出剩余的錢數,可以看到輸出為剩余錢數為200,-600,或-600,-600。這是不一定的,因為可能在第一次輸出剩余錢數之前,另一個線程可能還沒有將錢取走,也可能已經取走。
解決辦法
解決辦法:在getMoney()方法上加上關鍵字synchronized。
即程序改動后如下:(只是加了一個關鍵字)

public class FetchMoneyTest { public static void main(String[] args) { Bank bank = new Bank(); Thread t1 = new MoneyThread(bank);// 從銀行取錢 Thread t2 = new MoneyThread(bank);// 從取款機取錢 t1.start(); t2.start(); } } class Bank { private int money = 1000; public synchronized int getMoney(int number) { if (number < 0) { return -1; } else if (number > money) { return -2; } else if (money < 0) { return -3; } else { try { Thread.sleep(1000); } catch (InterruptedException e) { e.printStackTrace(); } money -= number; System.out.println("Left Money: " + money); return number; } } } class MoneyThread extends Thread { private Bank bank; public MoneyThread(Bank bank) { this.bank = bank; } @Override public void run() { System.out.println(bank.getMoney(800)); } }
此時輸出結果如下:
Left Money: 200
800
-2
表明第一次取款800元后,剩余200元,當另一個線程再去取的時候,已經不能再取錢了。
一個線程開始執行取錢的方法之后就阻止了其他線程再去執行這個方法,直到本線程結束,其他線程才有訪問權利。
當synchronized關鍵字修飾一個方法的時候,該方法叫做同步方法。
Java中的每個對象都有一個鎖(lock),或者叫做監視器(monitor),當一個線程訪問某個對象的synchronized方法時,將該對象上鎖,其他任何線程都無法再去訪問該對象的synchronized方法了。直到之前的那個線程執行方法完畢后(或者是拋出了異常),才將該對象的鎖釋放掉,其他線程才有可能再去訪問該對象的synchronized方法。
注意這時候是給對象上鎖,如果是不同的對象,則各個對象之間沒有限制關系。
參考資料
聖思園張龍老師Java SE系列視頻教程。