方法一:
編寫代碼實現切換邏輯
using System;
using System.Threading;
namespace 交替吃蘋果
{
class Program
{
// 共享資源:表示當前剩餘的蘋果數量
// 使用 private static 修飾,因為它需要被多個線程訪問,且屬於類級別的變量
private static int _appleCount = 10;
static void Main(string[] args)
{
Console.WriteLine("遊戲開始:張三和李四開始交替吃蘋果!");
Console.WriteLine("-------------------------------------\n");
// 1. 創建兩個 AutoResetEvent 對象,用於線程間的信號通信
// AutoResetEvent 是一個線程同步事件,它有兩種狀態:有信號(Signaled)和無信號(Non-Signaled)。
// 線程可以調用 WaitOne() 方法來等待信號,如果事件是有信號狀態,WaitOne() 會立即返回,並將事件重置為無信號狀態。
// 如果事件是無信號狀態,WaitOne() 會阻塞線程,直到另一個線程調用 Set() 方法將其設置為有信號狀態。
// lisiEvent: 初始狀態為有信號(true),這意味着李四線程可以先執行
AutoResetEvent lisiEvent = new AutoResetEvent(initialState: true);
// zhangsanEvent: 初始狀態為無信號(false),這意味着張三線程需要等待信號
AutoResetEvent zhangsanEvent = new AutoResetEvent(initialState: false);
// 2. 創建並定義李四的線程
// 使用 Lambda 表達式來定義線程要執行的代碼塊
Thread lisiThread = new Thread(() =>
{
// 這是一個無限循環,線程會一直執行,直到蘋果吃完
while (true)
{
// 等待 lisiEvent 信號。
// - 如果信號是“有”(初始狀態),線程會繼續執行,並將 lisiEvent 自動重置為“無”。
// - 如果信號是“無”,線程會在這裏阻塞(暫停),直到有其他線程調用 lisiEvent.Set()。
lisiEvent.WaitOne();
// 3. 臨界區:訪問共享資源前的檢查
// 由於兩個線程都可能修改 _appleCount,所以在操作前必須再次檢查。
// 這是為了防止在一個線程吃完最後一個蘋果後,另一個線程才被喚醒並嘗試再次吃蘋果。
if (_appleCount <= 0)
{
// 如果蘋果已經吃完,當前線程(李四)需要喚醒另一個線程(張三),
// 否則張三線程會永遠阻塞在 zhangsanEvent.WaitOne(),導致程序無法正常結束。
zhangsanEvent.Set();
break; // 跳出循環,結束當前線程
}
// 4. 執行“吃蘋果”的業務邏輯
Console.WriteLine("李四正在吃蘋果...");
_appleCount--; // 蘋果數量減一(共享資源被修改)
Console.WriteLine($"李四吃完一個蘋果。剩餘蘋果: {_appleCount} 個\n");
// 5. 通知張三線程可以開始吃蘋果了
// 將 zhangsanEvent 設置為“有”信號。
// 這會喚醒正在等待 zhangsanEvent 信號的張三線程。
zhangsanEvent.Set();
}
// 線程執行完畢後會自動終止
});
// 設置線程的名稱,方便在調試時識別
lisiThread.Name = "李四線程";
// 6. 創建並定義張三的線程
// 邏輯與李四線程對稱
Thread zhangsanThread = new Thread(() =>
{
while (true)
{
// 等待 zhangsanEvent 信號。
// 初始狀態為“無”,所以張三線程會在這裏等待,直到李四吃完一個蘋果並調用 zhangsanEvent.Set()。
zhangsanEvent.WaitOne();
// 同樣,在訪問共享資源前檢查蘋果是否還有剩餘
if (_appleCount <= 0)
{
// 喚醒李四線程,確保它也能正常結束
lisiEvent.Set();
break; // 跳出循環,結束當前線程
}
// 執行“吃蘋果”的業務邏輯
Console.WriteLine("張三正在吃蘋果...");
_appleCount--; // 蘋果數量減一(共享資源被修改)
Console.WriteLine($"張三吃完一個蘋果。剩餘蘋果: {_appleCount} 個\n");
// 通知李四線程可以開始吃下一個蘋果了
lisiEvent.Set();
}
// 線程執行完畢後會自動終止
});
zhangsanThread.Name = "張三線程";
// 7. 啓動兩個線程
// 此時,兩個線程會開始執行它們各自的 while 循環中的代碼。
// 由於 lisiEvent 初始為有信號,李四線程會先執行。
lisiThread.Start();
zhangsanThread.Start();
// 8. 讓主線程等待子線程執行完畢
// Join() 方法會阻塞當前的主線程,直到調用它的線程(lisiThread 和 zhangsanThread)執行完畢。
// 這可以防止主線程在子線程完成任務前就打印“程序結束”並退出。
lisiThread.Join();
zhangsanThread.Join();
// 9. 所有子線程執行完畢,遊戲結束
Console.WriteLine("-------------------------------------");
Console.WriteLine("遊戲結束:蘋果已全部吃完!");
}
}
}
測試演示
方法二:
編寫代碼實現切換邏輯
namespace _02_交替吃蘋果線程同步案例
{
internal class Program
{
// 定義一個共享資源 蘋果數量
private static int AppleNumber = 10;
private static Thread threadA;
private static Thread threadB;
static void Main(string[] args)
{
threadA = new Thread(EatApple);
threadB = new Thread(EatApple);
threadA.Name = "張三";
threadB.Name = "李四";
threadA.Start();
threadB.Start();
}
// 定義一個常量
private static readonly object locker = new object();
private static void EatApple()
{
while (true)
{
// 添加Lock鎖
lock (locker)
{
Console.WriteLine(Thread.CurrentThread.Name + "正在吃蘋果");
Thread.Sleep(3000);
// 蘋果的數量減少一個
AppleNumber--;
Console.WriteLine(Thread.CurrentThread.Name+"吃完了,還剩"+AppleNumber+"個蘋果\n");
// 當蘋果的數量小於等於1的時候
if (AppleNumber <= 1)
{
break;
}
}
}
}
}
}
測試演示
本文章為轉載內容,我們尊重原作者對文章享有的著作權。如有內容錯誤或侵權問題,歡迎原作者聯繫我們進行內容更正或刪除文章。