和for以及if关键字一样,如果被lock关键字定义的块仅包含一条指令,就不再需要花括号。我们可以再次改写为:
使用lock关键字将引导C#编译器创建出相应的try/finally块,这样仍旧可以预期到任何可能引发的异常。可以使用Reflector或者ildasm.exe工具验证这一点。
5.6.3 SyncRoot模式
和前面的例子一样,我们通常在一个静态方法中使用Monitor类配合一个Type类的实例。同样,我们往往会在一个非静态方法中使用this关键字来实现同步。在两种情况下,我们都是通过一个在类外部可见的对象对自身进行同步。如果其他部分的代码也利用这些对象来实现自身的同步,就会出现问题。为了避免这种潜在的问题,我们推荐使用一个类型为object的名为SyncRoot的私有成员,至于该成员是静态的还是非静态的则由需要而定。 例5-8
System.Collections.ICollection接口提供了object类型的SyncRoot{get;}属性。大多数的集合类(泛型或非泛型)都实现了该接口。同样地,可以使用该属性同步
对集合中元素的访问。不过在这里SyncRoot模式并没有被真正的应用,因为我们对访问进行同步所使用对象不是私有的。 例5-9
5.6.4 线程安全类
若一个类的每个实例在同一时间不能被一个以上的线程所访问,则该类称之为一个线程安全的类。为了创建一个线程安全的类,只需将我们见过的SyncRoot模式应用于它所包含的方法。如果一个类想变成线程安全的,而又不想为类中代码增加过多负担,那么有一个好方法就是像下面这样为其提供一个经过线程安全包装的继承类。 例5-10
另一种方法就是使用
System.Runtime.Remoting.Contexts.SynchronizationAttribute,这点我们将在本章稍后讨论。
5.6.5 Monitor.TryEnter()方法
该方法与Enter()相似,只不过它是非阻塞的。如果资源的独占访问权已经被另一个线程占据,该方法将立即返回一个false返回值。我们也可以调用TryEnter()方法,让它以毫秒为单位阻塞一段有限的时间。因为该方法的返回结果并不确定,并且当获得独占访问权后必须在finally子句中释放该权力,所以建议当TryEnter()失败时立即退出正在调用的函数: 例5-11[2]
5.6.6 Monitor类的Wait()方法, Pulse()方法以及PulseAll()方法
Wait()、Pulse()与PulseAll()方法必须在一起使用并且需要结合一个小场景才能被正确理解。我们的想法是这样的:一个线程获得了某个对象的独占访问权,而它决定等待(通过调用Wait())直到该对象的状态发生变化。为此,该线程必须暂时失去对象独占访问权,以便让另一个线程修改对象的状态。修改对象状态的线程必须使用Pulse()方法通知那个等待线程修改完成。下面有一个小场景具体说明了这一情况。
? ? ?
拥有OBJ对象独占访问权的T1线程,调用Wait(OBJ)方法将它自己注册到OBJ对象的被动等待列表中。
由于以上的调用,T1失去了对OBJ的独占访问权。因此,另一个线程T2通过调用Enter(OBJ)获得OBJ的独占访问权。
T2最终修改了OBJ的状态并调用Pulse(OBJ)通知了这次修改。该调用将导致OBJ被动等待列表中的第一个线程(在这里是T1)被移到OBJ的主动等待列表的首位。
? ?
而一旦OBJ的独占访问权被释放,OBJ主动等待列表中的第一个线程将被确保获得该权力。然后它就从Wait(OBJ)方法中退出等待状态。 在我们的场景中,T2调用Exit(OBJ)以释放对OBJ的独占访问权,接着T1恢复访问权并从Wait(OBJ)方法中退出。
PulseAll()将使得被动等待列表中的线程全部转移到主动等待列表中。注意这些线程将按照它们调用Wait()的顺序到达非阻塞态。
如果Wait(OBJ)被一个调用了多次Enter(OBJ)的线程所调用,那么该线程将需要调用相同次数的Exit(OBJ)以释放对OBJ的访问权。即使在这种情况下,另一个线程调用一次Pulse(OBJ)就足以将第一个线程变成非阻塞态。
下面的程序通过ping与pong两个线程以交替的方式使用一个ball对象的访问权来演示该功能。 例5-12
该程序输出(以不确定的方式):
pong线程没有结束并且仍然阻塞在Wait()方法上。由于pong线程是第二个获得ball对象的独占访问权的,所以才导致了该结果。
百度搜索“77cn”或“免费范文网”即可找到本站免费阅读全部范文。收藏本站方便下次阅读,免费范文网,提供经典小说综合文库线程与进程区别(5)在线全文阅读。
相关推荐: