方法一:
编写代码实现切换逻辑
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;
}
}
}
}
}
}
测试演示

希望对大家有所帮助。感谢大家的关注和点赞。

被折叠的 条评论
为什么被折叠?



