当有一个线程在对内存进行操作时,其他线程都不可以对这个内存地址进行操作。直到该线程完成操作,其他线程才能对该内存地址进行操作,而其他线程又处于等待状态,这种时候很容易出现问题
争用条件
如果两个或多个线程访问相同的对象。并且对共享状态的访问没有同步,就会出现争用条件
class StateObject{private int state = 5;public void ChangeState(){state++;if (state == 5){Console.WriteLine("value=5");}state = 5;}}
public static void ThreadSync_Lock_Example_01(){StateObject m = new StateObject();Thread t1 = new Thread(ChangeState);t1.Start(m);Console.ReadKey();static void ChangeState(object o){StateObject m = o as StateObject;while (true){m.ChangeState();}}}
此时运行程序是没有输出的,因为StateObject类中state初始值是5,if条件不会进入,随后又将state从新初始化为5
两个线程执行
public static void ThreadSync_Lock_Example_02(){StateObject m = new StateObject();Thread t1 = new Thread(ChangeState);Thread t2 = new Thread(ChangeState);t1.Start(m);t2.Start(m);Console.ReadKey();static void ChangeState(object o){StateObject m = o as StateObject;while (true){m.ChangeState();}}}
此时会不停的打印”value=5”,原因在于:一个线程在判断语句处时,另一个线程可能又将state的值改为了5,而导致输出合法
死锁
class Deadlock{static StateObject o1 = new StateObject();static StateObject o2 = new StateObject();public static void DeadlockA(object o){lock (o1){Console.WriteLine("我是线程{0},我锁定了对象o1", o);lock (o2){Console.WriteLine("我是线程{0},我锁定了对象o2", o);}}}public static void DeadlockB(object o){lock (o2){Console.WriteLine("我是线程{0},我锁定了对象o2", o);lock (o1){Console.WriteLine("我是线程{0},我锁定了对象o1", o);}}}}
Thread t1 = new Thread(Deadlock.DeadlockA);Thread t2 = new Thread(Deadlock.DeadlockB);t1.Start("t1");t2.Start("t2");

t1线程执行DeadlockA()方法顺序锁定o1和o2t2线程执行DeadlockB()方法顺序锁定o2和o1
当前结果显示t1线程锁定了o1后,t2线程在t1线程锁定o1后抢占进来,锁定了o2。t2在等t1解锁,t1在等t2解锁,都处于挂起状态在等对方解锁,这就形成了死锁,线程将无限等待下去
这个问题应该从一开始就设计好锁定顺序,也可以为锁定义超时时间来处理,保证“上锁”这个操作在一个线程上执行也是避免死锁的方法之一
C#中有多个用于多线程的同步技术
- lock语句
- Interlocked类
- Monitor类
- SpinLock类
- WaitHandle类
- Mutex类
- Semapphore类
- Event类
- Barrier类
- ReaderWriteLockSlim类
其中lock语句/Interlocked类/Monitor类可用于进程内存的同步,其它几个提供了多进程之间的线程同步
Lock
C#使用lock语句锁定在线程中共享的变量,如果一个线程锁定了变量,另一个线程就必须等待该锁定的解除
static void ChangeState(object o){StateObject m = o as StateObject;while (true){//给变量m加锁lock (m){m.ChangeState();}}}
因为实例的对象也可以用于外部的同步访问,而且不能在类自身控制这种访问,所以应采用SyncRoot模式,创建私有对象,将这个对象用于lock语句
private static object syncRoot= new object();static void ChangeState(object o){StateObject m = o as StateObject;while (true){lock (aync){m.ChangeState();}}}
需要注意
lock只能锁定对象,即引用类型,不能锁定值类型lock不能锁定空值,因为null是不需要被释放的- 不能锁定
string类型,虽然它也是引用类型的。因为字符串类型被CLR暂留,这意味着整个程序中任何给定字符串都只有一个实例,具有相同内容的字符串上放置了锁,就将锁定应用程序中该字符串的所有实例 - 避免锁定
public类型,如果该实例可以被公开访问,则lock(this)可能会有问题,因为不受控制的代码也可能会锁定该对象
锁是否必须是静态类型?
如果被锁定的方法是静态的,那么这个锁必须是静态类型。这样就是在全局锁定了该方法,不管该类有多少个实例,都要排队执行
如果被锁定的方法不是静态的,那么不能使用静态类型的锁,因为被锁定的方法是属于实例的。只要该实例调用锁定方法不产生损坏就可以,不同实例间是不需要锁的。这个锁只锁该实例的方法,而不是锁所有实例的方法
class ThreadSafe{private static object _locker = new object();void Go(){lock (_locker){......//共享数据的操作 (Static Method),使用静态锁确保所有实例排队执行}}private object _locker2=new object();void GoTo(){lock(_locker2)//共享数据的操作,非静态方法,是用非静态锁,确保同一个实例的方法调用者排队执行}}
同步对象可以兼作它lock的对象
如:
class ThreadSafe{private List <string> _list = new List <string>();void Test(){lock (_list){_list.Add ("Item 1");}}}
Monitors
lock其实是Monitors的简洁写法
lock (syncRoot){m.ChangeState();}
两者其实是一样的
Monitor.Enter(syncRoot);try{m.ChangeState();}finally{Monitor.Exit(syncRoot);}
Mutex
互斥锁是一个互斥的同步对象,同一时间有且仅有一个线程可以获取它。可以实现进程级别上线程的同步
public static void ThreadSync_Mutex_Example_01(){for (int i = 0; i < 3; i++){//在不同的线程中调用受互斥锁保护的方法Thread test = new Thread(MutexMethod);test.Start();}Console.Read();static void MutexMethod(){//实例化一个互斥锁Mutex mutex = new Mutex();Console.WriteLine("{0} 请求获取互斥锁", Thread.CurrentThread.ManagedThreadId);mutex.WaitOne();Console.WriteLine("{0} 已获取到互斥锁", Thread.CurrentThread.ManagedThreadId);Console.WriteLine("{0} 准备释放互斥锁", Thread.CurrentThread.ManagedThreadId);// 释放互斥锁mutex.ReleaseMutex();Console.WriteLine("{0} 已经释放互斥锁", Thread.CurrentThread.ManagedThreadId);}}
互斥锁的带有三个参数的构造函数
initiallyOwned: 如果initiallyOwned为true,互斥锁的初始状态就是被所实例化的线程所获取,否则实例化的线程处于未获取状态name:该互斥锁的名字,在操作系统中只有一个命名为name的互斥锁mutex,如果一个线程得到这个name的互斥锁,其他线程就无法得到这个互斥锁了,必须等待那个线程对这个线程释放createNew:如果指定名称的互斥体已经存在就返回false,否则返回true
