wait():使一個線程處於等待(阻塞)狀態,並且釋放所持有的對象的鎖;sleep():使一個正在運行的線程處於睡眠狀態,是一個靜態方法,調用此方法要處理 InterruptedException 異常;
notify():喚醒一個處於等待狀態的線程,當然在調用此方法的時候,並不能確切的喚醒某一個等待狀態的線程,而是由 JVM 確定喚醒哪個線程,而且與優先級無關;
notityAll():喚醒所有處於等待狀態的線程,該方法並不是將對象的鎖給所有線程,而是讓它們競爭,只有獲得鎖的線程才能進入就緒狀態;
提示:關於 Java 多線程和並發編程的問題,建議大家看我的另一篇文章《關於 Java並發編程的總結和思考》。
補充:Java 5 通過 Lock 接口提供了顯式的鎖機制(explicit lock),增強了靈活性以及對線程的協調。Lock 接口中定義了加鎖(lock())和解鎖(unlock())的方法,同時還提供了 newCondition()方法來產生用於線程之間通信的 Condition 對象;此外,Java 5 還提供了信號量機制(semaphore),信號量可以用來限制對某個共享資源進行訪問的線程的數量。在對資源進行訪問之前,線程必須得到信號量的許可(調用 Semaphore 對象的 acquire()方法);在完成對資源的訪問后,
線程必須向信號量歸還許可(調用 Semaphore 對象的 release()方法)。
下面的例子演示了 100 個線程同時向一個銀行賬戶中存入 1 元錢,在沒有使用同步機制和使用同步機制情況下的執行情況。
銀行賬戶類:
/**
* 銀行賬戶
* @author
*
*/
public class Account {
private double balance;
// 賬戶余額
/**
* 存款
* @param money 存入金額
*/
public void deposit(double money) {
double newBalance = balance + money;
try {
Thread.sleep(10);
// 模擬此業務需要一段處理時間
}
catch(InterruptedException ex) {
ex.printStackTrace();
}
balance = newBalance;
}
/**
* 獲得賬戶余額
*/
public double getBalance() {
return balance;
}
}
存錢線程類:
/**
* 存錢線程
* @author
*
*/
public class AddMoneyThread implements Runnable {
private Account account;
// 存入賬戶
private double money;
// 存入金額
public AddMoneyThread(Account account, double money) {
this.account = account;
this.money = money;
}
@Override
public void run() {
account.deposit(money);
}
}
測試類:
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
public class Test01 {
public static void main(String[] args) {
Account account = new Account();
ExecutorService service = Executors.newFixedThreadPool(100);
for(int i = 1; i <= 100; i++) {
service.execute(new AddMoneyThread(account, 1));
}
service.shutdown();
while(!service.isTerminated()) {}
System.out.println("賬戶余額: " + account.getBalance());
}
}
在沒有同步的情況下,執行結果通常是顯示賬戶余額在 10 元以下,出現這種狀況的原因是,當一個線程 A 試圖存入 1 元的時候,另外一個線程 B 也能夠進入存款
的方法中,線程 B 讀取到的賬戶余額仍然是線程 A 存入 1 元錢之前的賬戶余額,因此也是在原來的余額 0 上面做了加 1 元的操作,同理線程 C 也會做類似的事情,
所以最后 100 個線程執行結束時,本來期望賬戶余額為 100 元,但實際得到的通常在 10 元以下(很可能是 1 元哦)。解決這個問題的辦法就是同步,當一個線程
對銀行賬戶存錢時,需要將此賬戶鎖定,待其操作完成后才允許其他的線程進行操作,代碼有如下幾種調整方案:
在銀行賬戶的存款(deposit)方法上同步(synchronized)關鍵字
/**
* 銀行賬戶
* @author
*
*/
public class Account {
private double balance;
// 賬戶余額
/**
* 存款
* @param money 存入金額
*/
public synchronized void deposit(double money) {
double newBalance = balance + money;
try {
Thread.sleep(10);
// 模擬此業務需要一段處理時間
}
catch(InterruptedException ex) {
ex.printStackTrace();
}
balance = newBalance;
}
/**
* 獲得賬戶余額
*/
public double getBalance() {
return balance;
}
}
在線程調用存款方法時對銀行賬戶進行同步
/**
* 存錢線程
* @author 駱昊
*
*/
public class AddMoneyThread implements Runnable {
private Account account;
// 存入賬戶
private double money;
// 存入金額
public AddMoneyThread(Account account, double money) {
this.account = account;
this.money = money;
}
@Override
public void run() {
synchronized (account) {
account.deposit(money);
}
}
}
通過 Java 5 顯示的鎖機制,為每個銀行賬戶創建一個鎖對象,在存款操
作進行加鎖和解鎖的操作
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
/**
* 銀行賬戶
*
* @author 駱昊
*
*/
public class Account {
private Lock accountLock = new ReentrantLock();
private double balance; // 賬戶余額
/**
* 存款
*
* @param money
*
存入金額
*/
public void deposit(double money) {
accountLock.lock();
try {
double newBalance = balance + money;
try {
Thread.sleep(10); // 模擬此業務需要一段處理時間
}
catch (InterruptedException ex) {
ex.printStackTrace();
}
balance = newBalance;
}
finally {
accountLock.unlock();
}
}
/**
* 獲得賬戶余額
*/public double getBalance() {
return balance;
}
}
按照上述三種方式對代碼進行修改后,重寫執行測試代碼 Test01,將看到最終的賬戶余額為 100 元。當然也可以使用 Semaphore 或 CountdownLatch 來實現同步。