Java - 同步

你好,未來的Java巫師們!今天,我們將深入Java編程中最重要的概念之一:同步。如果你是編程新手,不必擔心;我會一步一步引導你完成這次旅程,就像我多年教學中為無數學生所做的那樣。所以,拿起你最喜歡的飲料,放鬆一下,讓我們一起踏上這次激動人心的冒險吧!

Java - Synchronization

什麼是同步,為什麼我們需要它?

想象一下,你在一個忙碌的廚房裡,有 多位廚師試圖準備一道複雜的菜餚。如果他們都沒有協調就伸手去拿食材,那麼混亂就會產生!這正是當多個線程嘗試同時訪問Java程序中的共享資源時可能發生的情況。在這裡,同步就成了解救的功臣!

Java中的同步就像在這個忙碌廚房中的交通警察,確保一次只有一位廚師(線程)可以使用特定的食材(資源)。它有助於維持秩序並防止衝突,確保我們的程序平順運行,不會有任何意外的驚喜。

線程同步的必要性

讓我們通過一個真實世界的例子來了解為什麼同步如此重要:

public class BankAccount {
private int balance = 1000;

public void withdraw(int amount) {
if (balance >= amount) {
System.out.println(Thread.currentThread().getName() + " 即將提取...");
balance -= amount;
System.out.println(Thread.currentThread().getName() + " 已提取 " + amount);
} else {
System.out.println("對不起," + Thread.currentThread().getName() + " 的餘額不足");
}
}

public int getBalance() {
return balance;
}
}

在這個例子中,我們有一個簡單的BankAccount類,其中有一個withdraw方法。看起來很直接,對嗎?但是當兩個人同時嘗試提取金錢時會發生什麼呢?讓我們來看看!

public class UnsynchronizedBankDemo {
public static void main(String[] args) {
BankAccount account = new BankAccount();

Thread john = new Thread(() -> {
account.withdraw(800);
}, "John");

Thread jane = new Thread(() -> {
account.withdraw(800);
}, "Jane");

john.start();
jane.start();

try {
john.join();
jane.join();
} catch (InterruptedException e) {
e.printStackTrace();
}

System.out.println("最終餘額: " + account.getBalance());
}
}

當你運行這個程序時,你可能会看到像這樣的結果:

John 即將提取...
Jane 即將提取...
John 已提取 800
Jane 已提取 800
最終餘額: -600

等等,怎麼了?我們怎麼會有負餘額?這個,我的朋友們,就是我們所稱的競態條件。John和Jane都檢查了餘額,看到有足夠的錢,並提取了。這正是為什麼我們需要同步!

在Java中實現同步

現在我們已經看到了問題,讓我們看看Java是如何幫助我們解決它的。Java提供了多種實現同步的方法,但我們將專注於兩種最常見的方法:

  1. 同步方法
  2. 同步塊

同步方法

添加同步的最簡單方法是在方法聲明中使用synchronized關鍵字。讓我們修改我們的BankAccount類:

public class BankAccount {
private int balance = 1000;

public synchronized void withdraw(int amount) {
if (balance >= amount) {
System.out.println(Thread.currentThread().getName() + " 即將提取...");
balance -= amount;
System.out.println(Thread.currentThread().getName() + " 已提取 " + amount);
} else {
System.out.println("對不起," + Thread.currentThread().getName() + " 的餘額不足");
}
}

public int getBalance() {
return balance;
}
}

現在,當我們運行我們的程序並使用這個同步方法時,我們得到了一個更合理的輸出:

John 即將提取...
John 已提取 800
對不起,Jane 的餘額不足
最終餘額: 200

好多了!一次只有一個線程可以執行withdraw方法,防止了我們之前的問題。

同步塊

有時,你可能不希望同步整個方法。在這些情況下,你可以使用同步塊。這裡是如何做到的:

public class BankAccount {
private int balance = 1000;
private Object lock = new Object(); // 這是我們的鎖對象

public void withdraw(int amount) {
synchronized(lock) {
if (balance >= amount) {
System.out.println(Thread.currentThread().getName() + " 即將提取...");
balance -= amount;
System.out.println(Thread.currentThread().getName() + " 已提取 " + amount);
} else {
System.out.println("對不起," + Thread.currentThread().getName() + " 的餘額不足");
}
}
}

public int getBalance() {
return balance;
}
}

這達到了與同步方法相同的結果,但給我們提供了對代碼中同步部分的更細粒度控制。

正確同步的重要性

現在,你可能在想,“太好了!我只需要同步一切!”但是慢慢來!過度同步可能會導致性能問題。這就像在一個小鎮的每個路口都設置交通信號燈——這可能很安全,但也會讓一切變得非常緩慢。

關鍵是只同步需要同步的部分。在我們的銀行賬戶示例中,我們只需要同步檢查和更新餘額的部分。

常見的同步方法

Java提供了多種有用的方法來處理同步。以下是 一些常見的方法的表格:

方法 描述
wait() 使當前線程等待,直到另一個線程調用notify()notifyAll()
notify() 喚醒正在等待此對象監視器的單個線程
notifyAll() 喚醒所有正在等待此對象監視器的線程
join() 等待線程死亡

這些方法在您需要更複雜的同步情節時非常有用。

結論

以上就是了,大家!我們已經走過了Java同步的領域,從理解我們為什麼需要它到在我們的代碼中實現它。請記住,同步就像烹飪中的調味料——只需足夠地增強你的程序,但不要太多,以免掩蓋其他一切。

當你繼續你的Java冒險時,你將遇到更多複雜的同步情節。但是不要害怕!有了這個基礎,你已經為應對任何多線程挑戰做好了準備。

繼續編碼,繼續學習,最重要的是,玩得開心!畢竟,編程既是科學也是藝術。直到下次,願你的線程同步,你的Java強大!

Credits: Image by storyset