方法一:

編寫代碼實現切換邏輯
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("遊戲結束:蘋果已全部吃完!");
        }
    }
}
測試演示

線程經典實例——吃蘋果問題_#visual studio

方法二:

編寫代碼實現切換邏輯
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;
                    }
                }
            }
        }
    }
}
測試演示

線程經典實例——吃蘋果問題_#visual studio_02