现在有五个工人在果园摘水果,一次只能摘一个,摘下的水果放入一个框中,这种框最多只能装50个橘子,一共有两个这样的框。当一个工人装框时,其他工人不能在使用这个框。当两个框都装满了,工人只有等框里有剩余位置后,才能在摘果子。然后,有四个小孩来框里拿橘子,一次最多拿一个,并且当一个小孩在框前拿橘子时,其他小孩只能到另一个框拿橘子,如果两个框前都有小孩拿橘子,那么剩余要拿橘子的小孩只能等待。(这个题目是我自己编的,可能不是很准确)
现在要设计一个程序模拟这样一个过程。
 
分析:框是互斥资源,每次放橘子前 得判断有没有空闲得框,有就占住加锁,然后里面执行放橘子得方法。放完之后再解锁。框可以用队列表示。
工人和小孩可以用Task模拟。
 
这里需要两种锁,一种是放橘子得时候得一把Monitor锁,一种是当没有空闲得框后,加的AutoResetEvent锁。
当使用两把锁得时候,需要特别小心,稍不注意都会引发死锁。
 
Monitor锁再使用得时候,得用引用变量作为加锁得对象,不要用字符串和值变量。虽然再用值变量时,编译器不会报错,但是运行时,Enter会装箱,把值变量变为引用变量,但是再Exit时,依然是个值变量,这样Enter和Exit的锁变量就不是同一个变量,造成找不到锁的情况,就会抛出异常。
 
另外使用Monitor枷锁时,应该使用 try{}finally{}语句块,保证总是会被解锁,否则遇到异常,不执行解锁语句,就死锁了。
其实lock语句块就是Monitor的简便方法,内部使用的还是Monitor。
 
对于AutoResetEvent而言,可以暂停和唤醒线程,再不同线程可以相互唤醒和阻塞。这样就非常的灵活。其实推荐使用ManualResetEvent,因为比起AutoResetEvent,可以唤起多个线程,如果说小孩一次拿多个橘子,这种方式就比AutoResetEvent有优势,因AutoResetEvent只唤醒一个线程。
 
线程的同步还有其他方法,比如再数值上同步 有InterLock,其他的如信号量(Sema'phore)同步,CountDownEvent。
同步的应用,还可以是用进程间同步的方法,实现在一台主机上,每次只能启动一个相同的应用程序,这时可以使用Mutex。
 
避免资源在线程同步中互斥,还可以用 线程本地存储技术,ThreadLocal,例子:
详见:《C#本质论》第三版,第19章
下面直接看代码:
 internal class Program
{
//最多容纳50个橘子每个框
static readonly int MAX = 50;
//两个框
static List<ConcurrentQueue<Orange>> Queues =
new List<ConcurrentQueue<Orange>>();
//记录空闲的框
static List<int> QidxBags = new List<int>(); static int MaxO = 1000; //最多摘1000个橘子 static readonly object Sync = new object();
static readonly object Sync2 = new object();
//比起AutoResetEvent,可以唤起多个线程,如果说小孩一次拿多个橘子,而不是一个,
//这种方式比AutoResetEvent有优势,因为AutoResetEvent只唤醒一个线程。
static ManualResetEvent MResetEvent = new ManualResetEvent(false); static void Main(string[] args)
{
Queues.Add(new ConcurrentQueue<Orange>());
Queues.Add(new ConcurrentQueue<Orange>()); for (int i = 0; i < Queues.Count; i++)
{
QidxBags.Add(i);
}
TaskProduceAndComsummer();
Console.ReadKey();
} static int GetQueuesIdx()
{
int idx = -1; int count = QidxBags.Count; if (count > 0)
{
return count;
} return idx;
} static bool IsEmpty()
{
foreach (var item in Queues)
{
if (item.Count >0)
{
return false;
}
}
return true;
} static bool IsFull()
{
foreach (var item in Queues)
{
if (item.Count < MAX)
{
return false;
}
}
return true;
} static void TaskProduceAndComsummer()
{
for (int i = 0; i < 5; i++)
{
string name = "工人_" + (i + 1);
Task t = new Task(Produce, (object)(name)); t.Start();
} for (int i = 0; i < 3; i++)
{
string name = "小孩_" + (i + 1);
Task t = new Task(Consumer, (object)(name)); t.Start();
} }
static void Produce(object name)
{
while (true&&MaxO>0)
{
int count = -1;
int iPos = -1;
lock (Sync2)
{
count = GetQueuesIdx();
}
if (count > 0&&!IsFull())
{
bool refTaken = false; Monitor.Enter(Sync, ref refTaken);
bool isPut = false;
try
{ for (int i = 0; i < count; i++)
{ iPos = QidxBags[i];
var q = Queues[iPos]; if (q.Count < MAX)
{
QidxBags.Remove(iPos);
q.Enqueue(Orange.GetOrange());
MaxO -= 1;
Console.WriteLine(name + ":+摘了一个橘子,放入框【" + iPos + "】中");
Console.WriteLine("框一数量:{0},框二数量{1}", Queues[0].Count, Queues[1].Count);
isPut = true;
//唤醒小孩线程
MResetEvent.Set();
break;
} }
}
finally
{
if (refTaken)
{
if (iPos > -1)
{
QidxBags.Add(iPos);
} Monitor.Exit(Sync);
if (!isPut)
{
Console.WriteLine("满了");
} }
}
}
else
{
MResetEvent.WaitOne();
} }
} static void Consumer(object name)
{
while (true)
{
int count = GetQueuesIdx();
int iPos = -1;
if (count > 0&&!IsEmpty())
{
bool refTaken = false;
bool isPut = false;
Monitor.Enter(Sync, ref refTaken);
try
{
for (int i = 0; i < count; i++)
{ iPos = QidxBags[i];
var q = Queues[iPos]; if (q.Count >0)
{
QidxBags.Remove(iPos);
Orange o = null;
q.TryDequeue(out o);
Console.WriteLine(name + ":+拿了一个橘子,从框【" + iPos + "】中");
Console.WriteLine("框一数量:{0},框二数量{1}", Queues[0].Count, Queues[1].Count);
isPut = true;
//框有容量了,可以放了,所以唤醒被阻塞得工人线程
MResetEvent.Set();
break;
} }
}
finally
{
if (refTaken)
{
if (iPos > -1)
{
QidxBags.Add(iPos);
}
Monitor.Exit(Sync);
if (!isPut)
{ Console.WriteLine("都空了"); } }
}
}
else
{
MResetEvent.WaitOne();//阻塞
}
}
}
} public class Orange
{
public static Orange GetOrange()
{
Random rand = new Random();
int t = rand.Next(10, 20);
Thread.Sleep(t);
return new Orange();
}
}

部分结果:

最新文章

  1. Linux自动共享USB设备:udev+Samba
  2. python3 下的文件输入输出特性以及如何覆盖文件内容和接下去输入
  3. MongoDB Windows环境安装及配置
  4. code review
  5. 请将 php.ini 中的 short_open_tag 设置为 On,否则无法继续安装。
  6. 集合运算(A-B)U(B-A)
  7. Java中Integer的最大值和最小值
  8. NOIP2011(提高组)DAY2---观光公交(vijosP1741)
  9. 参数TFilterPredicate 类型说明
  10. sublime 使用快捷记录
  11. python语言学习3 ——第一个python程序
  12. Java对字符串进行的操作
  13. Y2 MyBatis(二)
  14. 201621123060 《Java程序设计》第五周学习总结
  15. BZOJ3230 相似子串 【后缀数组】
  16. day08 python之函数初识
  17. vuex最简单、最详细的入门文档
  18. Mybatis类型转换介绍
  19. Nextcloud13私有云盘安装指南
  20. asp.net导出excel 问题及服务器的部署dcom组件配置

热门文章

  1. PlatformIO手工升级stcgal到1.6版本
  2. JAVAWEB使用保存cookie、删除cookie、获取cookie工具类
  3. 【LeetCode】20. Valid Parentheses 有效的括号
  4. hdu 4704 Sum(组合,费马小定理,快速幂)
  5. C9软件工程非一线城市面试经验
  6. Ubuntu 16.04远程配置Jupyter Notebook
  7. JAVA获取上下行网速
  8. AES对称加密算法实现:Java,C#,Golang,Python
  9. css 基础 rgba表示法
  10. JS 数组的基本使用和案例