*使用Monitor来同步(System.Threading.Monitor)
为了标识受保护代码段的开始和结束位置,需要分别调用静态方法Monitor.Enter()和Monitor.Exit()。在这两者之间的所有代码一定要用Try/finally块包围好,否则,在受保护的代码段内可能发生一个异常,造成Monitor.Exit()永远都无法被调用,从而长时间的阻止(block)其他线程。
class Program
{
readonly static object _Sync=new object();
const int _Total=9999;
static long _Count=0;
static void Main(string[] args)
{
Task task = Task.Factory.StartNew(Decrement);
for (int i = 0; i < _Total; i++)
{
bool lockTaken = false;
//获取指定对象上的排他锁,并自动设置一个值,指示是否获得了该锁。
//Monitor还有一个Monitor.Pulse()方法,允许线程进入“就绪队列”,下一个就轮到它获得锁。
Monitor.Enter(_Sync, ref lockTaken);
try
{
_Count++;
}
finally
{
if (lockTaken)
{
//释放指定对象上的排他锁。
Monitor.Exit(_Sync);
}
}
}
task.Wait();
Console.Write(_Count.ToString());
Console.ReadKey();
}
static void Decrement()
{
for (int i = 0; i < _Total; i++)
{
bool lockTaken = false;
Monitor.Enter(_Sync,ref lockTaken);
try
{
_Count--;
}
finally
{
if (lockTaken)
{
Monitor.Exit(_Sync);
}
}
}
}
}
**使用lock关键字
readonly static object _Sync=new object();
const int _Total=9999;
static long _Count=0;
static void Main(string[] args)
{
Task task = Task.Factory.StartNew(Decrement);
for (int i = 0; i < _Total; i++)
{
lock(_Sync)//lock的对象不能是值类型。
{
_Count++;
}
}
task.Wait();
Console.Write(_Count.ToString());
Console.ReadKey();
}
static void Decrement()
{
for (int i = 0; i < _Total; i++)
{
lock(_Sync)
{
_Count--;
}
}
}
**为什么要避免锁定this、typeof(type)、string ?
在this关键字锁定,以及针对静态数据,在从typeof(type)获取的类型实例上锁定。这样一来,在this的情况下,可以为一个特定对象实例关联的所有状态(所有数据)提供一个同步目标;在Typeof(Type)的情况下,可以为一个类型的所有静态数据提供一个同步目标。这样所的情况在于,在另一个完全不相干的代码块中,可能创建一个完全不同的同步快,而这个同步块的同步目标可能就是this(或者typeof(type))所指向的同步目标。最好在一个类中设一个私有的只读字段。
要避免的另一个锁定类型是string,这个是字符串留用(string interning)的原因。如果同一字符串在多个位置出现,那么所有位置都肯能引用同一个实例,使锁定的范围大于预期。
总之,锁定的目标应该是object类型的单位同步上下文实例。
**将字段声明为volatile:这个关键字强迫对volatile字段的所有读写操作都在代码指示的位置发生,而不是通过优化而生成的其他某个位置发生。
**使用System.Threading.Interlocked类:通常有处理器直接支持,而且面向特定的同步模式。
public static T CompareExchange<T>(T location ,Tvalue,T Comparand);
public static T Exchange<T>( T location ,T value);
public static int Decrement(ref int location);
public static int Increment(ref int location);
public static int Add(ref int locaiton ,int value);
public static long Read(ref long location);
**多个线程时的事件通知
假如多个线程可能同时访问一个委托,就需要对委托的赋值和触发进行同步。用于添加和删除侦听器的运算符是现行安全的,而且是静态的。
//not Thread -safe
if(OnTemperatureChange!=null)
{
//Call subscribers
onTemperatureChanged(this ,new TemperatureEventArgs(value));
}
//修改上面的代码,需要为一个副本赋值,检查副本是否为null,再处罚副本
TemperatureChangedHandler localOnChange= OnTemperatureChange;
if(localOnChange!=null )
{
localOnChanged(this,new TemperatureEventArgs(value));
}
**更多的同步类型。【,还需要理解啊~~~~】
1.System.Threading.Mutex:在概念上几乎与System.Threading.Monitor类的几乎完全一致,没有Pulse()方法支持,只是lock关键字同的不是他,而且它可以命名以支持多个进程之间的同步。使用Mutex(互斥)类,你可以同步对文件或者其他跨进程资源的访问。有于Mutex是一个跨进程的资源,所以.net2.0允许一个System.Security.AccessControl.MutexSecutity对象来设置访问控制。Mutex的一个用处限制应用程序,让它不能同时运行多个实例。
bool firstApplicationInstance;
// Assembly.GetEntryAssembly():Assembly 是默认应用程序域中的进程可执行文件,或是由
//System.AppDomain.ExecuteAssembly(System.String)
// 执行的第一个可执行文件。当从非托管代码调用时可返回 null。
string mutexName = Assembly.GetEntryAssembly().FullName;
//Mutex(false, mutexName, out firstApplicationInstance):使用一个指示调用线程是否应拥有互斥体的初始所属权的布尔值,
// 一个作为互斥体名称的字符串,以及一个在方法返回时指示调用线程是否被授予互斥体的初始所属权的布尔值来初始化 Mutex 类的新实例。
using (Mutex mutex = new Mutex(false, mutexName, out firstApplicationInstance))
{
if (!firstApplicationInstance)
{
Console.WriteLine("This application is already running.");
}
else
{
Console.WriteLine("Enter to shhutdown");
Console.ReadLine();
}
}
Mutex继承自System.Threading.WaitHandle,所以它包含WaitAll().WaitAny(),SignalAndWait(),可以自动获得多个锁(Monitor类不支持)。
2.WaitHandle:其关键方法是WaitOne() :阻塞当前线程,直到WaitHandle实例收到信号或者被设置(调用set())。WaitOne()的几个重载版本允许等待不确定的事件。
void WaitOne() 等待1毫秒
bool WaitOne(int milliseconds)
bool WaitOne(TimeSpan timeout)
只要在超时之前收到信号,就会返回一个true.
两个关键的静态成员:WaitAll()和WaitAny()
这两个方法也支持超时,除此之外,还要获得一个WaitHandle集合(以数组的形式),是他们能响应来自集合中的任何WaitHandle()的信号。
关于WaitHandle的最后一点要注意的是,它包含了一个实现了IDisposable的SafeWaitHandle类型的句柄。所以,当WaitHandle不再需要的时候,需要对他们进行释放。
3.重置事件类ManualResetEvent和ManualResetEventSlim:用于强迫代码等候另一个线程的执行,知道获得事件已经发生的通知。他们尤其适合用来测试多线程代码,因为有时需要先等待一个特定的状态,才能对结果进行验证。
关键方法Set()和Wait()。调用Wait()方法,会阻塞一个线程的执行,直到一个不同的线程调用Set(),或者直接设定的等待事件结束(超时)。
class Program
{
static ManualResetEventSlim MainSignaledResetEvent;
static ManualResetEventSlim DoWorkSignaledResetEvent;
public static void DoWork()
{
Console.WriteLine("DoWork() Sarted...");
//将事件状态设置为有信号,从而允许一个或多个等待该事件的线程继续。
DoWorkSignaledResetEvent.Set();
//阻止当前线程,直到设置了当前 System.Threading.ManualResetEventSlim 为止。
MainSignaledResetEvent.Wait();
Console.WriteLine("DoWork()ending...");
}
static void Main(string[] args)
{
using(MainSignaledResetEvent=new ManualResetEventSlim())
using (DoWorkSignaledResetEvent = new ManualResetEventSlim())
{
Console.WriteLine("Application started....");
Console.WriteLine("Starting task...");
Task task = Task.Factory.StartNew(DoWork);
DoWorkSignaledResetEvent.Wait();
Console.WriteLine("Threading executing...");
MainSignaledResetEvent.Set();
task.Wait();
Console.WriteLine("Thread completed");
Console.WriteLine("Application shutting down...");
Console.ReadKey();
}
}
}
结果显示:
Application started....
Starting task...
DoWork() Sarted...
Threading executing...
DoWork()ending...
Thread completed
Application shutting down...
**Semaphore/SemaphoreSlim和CountdownEvent
**并发集合类
**线程本地存储(Thread local Storage)
利用线程本地存储,线程就有了专属的变量的实例,这样一来,就没有同步的必要了。因为对只在单个线程的上下文中发生的数据进行同步是没有意义的。
1.ThreadLocal<T>
2.ThreadStaticAttribute。
。。。先放着吧,回头继续研究,脑子已经是浆糊了。。。。。