- 局部变量 方法参数 返回值 驻留在堆栈中 本来就具备线程安全性
实例和类变量在堆中 不是线程安全的 可以通过合适的设计类 来使之具备线程安全性 - 1、标识同步 代码中的重要部分
2、使对象不可改变
3、使用线程安全包装器 基于对象的包装器 - Monitor (对象锁)提供同步对对象的访问的机制。Monitor 类通过向单个线程授予对象锁来控制对对象的访问。对象锁提供限制访问代码块(通常称为临界区)的能力。当一个线程拥有对象的锁时,其他任何线程都不能获取该锁。还可以使用 Monitor 来确保不会允许其他任何线程访问正在由锁的所有者执行的应用程序代码节,除非另一个线程正在使用其他的锁定对象执行该代码。
注意 使用 Monitor 锁定对象(即引用类型)而不是值类型。有关详细信息,请参见 Enter 和概念主题 Monitor。
Monitor 具有以下功能:
它根据需要与某个对象相关联。
它是未绑定的,也就是说可以直接从任何上下文调用它。
不能创建 Monitor 类的实例。
将为每个同步对象来维护以下信息:
对当前持有锁的线程的引用。
对就绪队列的引用,它包含准备获取锁的线程。
对等待队列的引用,它包含正在等待锁定对象状态变化通知的线程。
Monitor 对象通过使用 Monitor.Enter、Monitor.TryEnter 和 Monitor.Exit 方法对特定对象获取锁和释放锁来公开同步访问代码区域的能力。在对代码区域获取锁后,就可以使用 Monitor.Wait、Monitor.Pulse 和 Monitor.PulseAll 方法了。如果锁被暂挂,则 Wait 释放该锁并等待通知。当 Wait 接到通知后,它将返回并再次获取该锁。Pulse 和 PulseAll 都会发出信号以便等待队列中的下一个线程继续执行。
Monitor 将锁定对象(即引用类型),而非值类型。尽管可以向 Enter 和 Exit 传递值类型,但对于每次调用它都是分别装箱的。因为每次调用都创建一个独立的对象,所以 Enter 永远不会阻塞,而且它要保护的代码并没有真正同步。另外,传递给 Exit 的对象不同于传递给 Enter 的对象,所以 Monitor 将引发 SynchronizationLockException,并显示以下消息:“从不同步的代码块中调用了对象同步方法。”
ms-help://MS.VSCC.2003/MS.MSDNQTR.2003FEB.2052/cpguide/html/cpconmonitor.htm - Mutex (互斥体) 同步基元也可用于进程间同步。当两个或更多线程需要同时访问一个共享资源时,系统需要使用同步机制来确保一次只有一个线程使用该资源。Mutex 是同步基元,它只向一个线程授予对共享资源的独占访问权。如果一个线程获取了互斥体,则要获取该互斥体的第二个线程将被挂起,直到第一个线程释放该互斥体。除了可以用于进程间同步外,和Moniter很相似
可以使用 WaitHandle.WaitOne 请求互斥体的所属权。拥有互斥体的线程可以在对 Wait 的重复调用中请求相同的互斥体而不会阻塞其执行。但线程必须调用 ReleaseMutex 方法同样多的次数以释放互斥体的所属权。如果线程在拥有互斥体期间正常终止,则互斥体状态设置为终止,并且下一个等待线程获得所属权。如果没有线程拥有互斥体,则互斥体状态为终止。 - AutoResetEvent 通知正在等待的线程已发生事件。
AutoResetEvent 允许线程通过发信号互相通信。通常,此通信涉及线程需要独占访问的资源。
线程通过调用 AutoResetEvent 上的 WaitOne 来等待信号。如果 AutoResetEvent 处于非终止状态,则该线程阻塞,并等待当前控制资源的线程通过调用 Set 发出资源可用的信号。
调用 Set 终止 AutoResetEvent 以释放等待线程。AutoResetEvent 将保持终止状态,直到一个正在等待的线程被释放,然后自动返回非终止状态。如果没有任何线程在等待,则状态将无限期地保持为终止状态。
可以通过将一个布尔值传递给构造函数来控制 AutoResetEvent 的初始状态,如果初始状态为终止状态,则为 true;否则为 false。
AutoResetEvent 还可以和静态(在 Visual Basic 中为 Shared)WaitAll 和 WaitAny 方法一起使用。 - ManualResetEvent 通知一个或多个正在等待的线程已发生事件。
ManualResetEvent 允许线程通过发信号互相通信。通常,此通信涉及一个线程在其他线程进行之前必须完成的任务。
当线程开始一个活动(此活动必须在其他线程进行之前完成)时,它调用 Reset 将 ManualResetEvent 设置为非终止状态。此线程可被视为控制 ManualResetEvent。调用 ManualResetEvent 上的 WaitOne 的线程将阻塞,并等待信号。当控制线程完成活动时,它调用 Set 以发出等待线程可以继续进行的信号。并释放所有等待线程。
一旦它被终止,ManualResetEvent 将保持终止状态,直到它被手动重置。即对 WaitOne 的调用将立即返回。
可以通过将布尔值传递给构造函数来控制 ManualResetEvent 的初始状态,如果初始状态处于终止状态,为 true;否则为 false。
ManualResetEvent 还可以和静态(在 Visual Basic 中为 Shared)WaitAll 和 WaitAny 方法一起使用。 - Interlocked 为多个线程共享的变量提供原子操作。
- WaitHandle 封装等待对共享资源的独占访问权的操作系统特定的对象。此类通常用作同步对象的基类。从 WaitHandle 派生的类定义一个信号传输机制以指示获取或释放对共享资源的独占访问,但使用继承的 WaitHandle 方法在等待对共享资源的访问时阻塞。使用此类的静态方法阻塞一个线程,直到一个或多个同步对象接收到信号。
- SynchronizationAttribute(System.EnterpriseServices.SynchronizationAttribute) 设置组件的同步值。
- SynchronizationAttribute(System.Runtime.Remoting.Contexts.SynchronizationAttribute)为当前上下文和所有共享同一实例的上下文强制一个同步域。
- MethodImplAttribute 指定如何实现某方法的详细信息。可将此属性应用于方法或构造函数。
- ReaderWriterLock 用于同步对资源的访问。在任一特定时刻,它允许多个线程同时进行读访问,或者允许单个线程进行写访问。在资源不经常发生更改的情况下,ReaderWriterLock 所提供的吞吐量比简单的一次只允许一个线程的锁(如 Monitor)更高。
在多数访问为读访问,而写访问频率较低、持续时间也比较短的情况下,ReaderWriterLock 的性能最好。多个读线程与单个写线程交替进行操作,所以读线程和写线程都不会长时间阻塞。
注意 长时间持有读线程锁或写线程锁会使其他线程发生饥饿 (starve)。为了得到最好的性能,需要考虑重新构造应用程序以将写访问的持续时间减少到最小。
一个线程可以持有读线程锁或写线程锁,但是不能同时持有两者。若要获取写线程锁,请使用 UpgradeToWriterLock 和 DowngradeFromWriterLock,而不要通过释放读线程锁的方式获取。
递归锁请求会增加锁上的锁计数。
读线程和写线程将分别排入各自的队列。当线程释放写线程锁时,此刻读线程队列中的所有等待线程都将被授予读线程锁;当已释放所有读线程锁时,写线程队列中处于等待状态的下一个线程(如果存在)将被授予写线程锁,依此类推。换句话说,ReaderWriterLock 在一组读线程和一个写线程之间交替进行操作。
当写线程队列中有一个线程在等待活动读线程锁被释放时,请求新的读线程锁的线程会排入读线程队列。即使它们能和现有的读线程锁持有者同时一起访问,也不会授予它们的请求;这可以防止写线程被读线程无限期阻塞。
大多数在 ReaderWriterLock 上获取锁的方法都采用超时值。使用超时可以避免应用程序中出现死锁。例如,某个线程可能获取了一个资源上的写线程锁,然后请求第二个资源上的读线程锁;同时,另一个线程获取了第二个资源上的写线程锁,并请求第一个资源上的读线程锁。如果不使用超时,这两个线程将出现死锁。
如果超时间隔过期并且没有授予锁请求,则此方法通过引发 ApplicationException 将控制返回给调用线程。线程可以捕捉此异常并确定下一步要进行的操作。 - ThreadStaticAttribute 标记为 ThreadStaticAttribute 的静态(在 Visual Basic 中为 Shared)字段不在线程之间共享。每个执行线程都有单独的字段实例,并且独立地设置及获取该字段的值。如果在不同的线程中访问该字段,则该字段将包含不同的值。
注意 不要为标记为 ThreadStaticAttribute 的字段指定初始值,因为这样的初始化只会发生一次,因此在类构造函数执行时只会影响一个线程。在不指定初始值的情况下,如果它是值类型,可依赖初始化为其默认值的字段,如果它是引用类型,则可依赖初始化为空引用(Visual Basic 中为 Nothing)的字段。 - 死锁 当2个或更多线程等待释放2个或更多的锁时 会发生死锁;阻止潜在死锁的最好方法之一:避免一次取得多个锁;好的锁定方案会以一种定义完好的方式整合所有线程对锁的获得 锁定方案的测试很重要 没有标准的策略可以避免所有的死锁