Lock是将一段代码定义为临界区,临界区中的代码在同一时刻只能有一个线程访问,
当临界区代码执行时,其他线程调用会被阻塞,需等待资源释放。
语法:
private object locker=new object();
void Method()
{
lock(locker)
{
代码块…
}
}
注意:
1 lock不能锁定空值
2 lock不能string类型,因为字符驻留机制,无法被释放
3 lock不能锁定值类型
4 lock避免锁定public类型或不受程序控制的对象,容易出现死锁
实例
基础类
1 class CTest 2 { 3 private bool deadlocked = true; 4 private object locker = new object(); 5 public void LockMe(object o) 6 { 7 lock(locker) 8 { 9 while(deadlocked) 10 { 11 deadlocked=(bool)o; 12 Console.WriteLine("Foo:I am locked :("); 13 Thread.Sleep(500); 14 } 15 } 16 } 17 public void DoNotLockMe() 18 { 19 Console.WriteLine("I am not locked:)"); 20 } 21 }
操作
1 static void Main(string[] args) 2 { 3 CTest c1 = new CTest(); 4 Thread th = new Thread(c1.LockMe); 5 th.Start(true); 6 Thread.Sleep(100); 7 lock (c1) 8 { 9 c1.DoNotLockMe(); 10 c1.LockMe(false); 11 } 12 Console.ReadKey(); 13 }
显示效果:
Monitor
lcok的底层是Monitor.Enter和Moniter.Exit,有了lock语法糖可以轻松实现加锁操作,为了更精确的操作,需要使用Monitor类
Monitor.Enter 上锁,锁定资源
Monitor.TryEnter 在指定超时时间内锁定资源,可避免死锁
Monitor.Wait 暂时释放资源
Monitor.Pulse 唤醒等待队列中的线程
Monitor.Exit 释放资源
实例:
打印奇偶数方法
1 public void PrintEven() 2 { 3 Monitor.Enter(this); 4 try 5 { 6 for (int i = 0; i <= 10; i = i + 2) 7 { 8 Console.WriteLine(Thread.CurrentThread.Name + "------" + i); 9 } 10 } 11 catch (Exception) 12 { 13 14 throw; 15 } 16 finally 17 { 18 Monitor.Exit(this); 19 } 20 } 21 22 public void PrintOdd() 23 { 24 Monitor.Enter(this); 25 try 26 { 27 for (int i = 1; i <= 10; i = i + 2) 28 { 29 Console.WriteLine(Thread.CurrentThread.Name + "------" + i); 30 } 31 } 32 catch (Exception) 33 { 34 35 throw; 36 } 37 finally 38 { 39 Monitor.Exit(this); 40 } 41 42 }
操作
1 static void Main(string[] args) 2 { 3 4 Program p = new Program(); 5 Thread the = new Thread(p.PrintEven); 6 the.IsBackground = true; 7 the.Name = "打印偶数"; 8 the.Start(); 9 Thread tho = new Thread(p.PrintOdd); 10 tho.IsBackground = true; 11 tho.Name = "打印奇数"; 12 tho.Start(); 13 14 Console.ReadKey(); 15 }
显示效果:
ReaderWriterLock
主要解决类似数据库这种读取数据多写入数据少的情况,如用Monitor类则形成独占,不能实现多个线程读取数据,读写锁很好的解决了这一情况
主要包括以下几个方法:
AcquireReaderLock 获取读取锁
ReleaseReaderLock 释放读取锁
AcquireWriterLock 获取写入锁
ReleaseWriterLock 释放写入锁
UpgradeToWriterLock 读锁转换为写锁
DowngradeFromWriterLock 写锁转换为读锁
Mutex
跨多个线程同步访问的类,只有一个线程能获得互斥锁定,访问受互斥保护的同步代码区域。
WaitOne 等待资源释放
ReleaseMutex 释放资源
最常用的是程序启动时判断是否已有实例在运行
代码如下:
1 static void Main() 2 { 3 bool flag = false; 4 bool requestInitialOwnership = true; 5 Mutex mt = new Mutex(requestInitialOwnership, "MutexTest1", out flag); 6 if (flag) 7 { 8 Application.EnableVisualStyles(); 9 Application.SetCompatibleTextRenderingDefault(false); 10 Application.Run(new Form1()); 11 12 } 13 else 14 { 15 MessageBox.Show("MutexTest1已启动"); 16 Application.Exit(); 17 } 18 19 }
显示效果:
排队买票,同一窗口在同一时间只能有一个人买票
买票方法
1 private static Mutex mt = new Mutex(); 2 public static void buyTichets(Object name) 3 { 4 if (mt.WaitOne()) 5 { 6 try 7 { 8 Console.WriteLine("{0}占用窗口并开始买票",name); 9 Thread.Sleep(1000); 10 } 11 catch (Exception) 12 { 13 14 throw; 15 } 16 finally 17 { 18 Console.WriteLine("{0}离开窗口",name); 19 mt.ReleaseMutex(); 20 } 21 } 22 }
操作
1 static void Main(string[] args) 2 { 3 Thread th1 = new Thread(buyTichets); 4 th1.Start("小明"); 5 Thread th2 = new Thread(buyTichets); 6 th2.Start("小A"); 7 Console.ReadKey(); 8 }
显示效果:
Interlocked
为多线程共享整数变量提供原子操作,类似操作系统的PV操作
Interlocked.Read 读取计数器的值
Interlocked.Add 使计数器增加指定的值
Interlocked.Increment 使计数器加一
Interlocked.Decrement 使计数器减一
Interlocked.Exchange 把计数器设定为某个指定值
Interlocked.CompareExchange 将计数器与某个值比较,若相等则计数器设定为指定的值
实例
1 private static char buffer; 2 private static long used = 0; 3 static void Main(string[] args) 4 { 5 string str = "壬戌之秋,七月既望,苏子与客泛舟游于赤壁之下。清风徐来,水波不兴。举酒属客,诵明月之诗,歌窈窕之章。"; 6 Thread thWriter=new Thread(delegate() 7 { 8 for (int i = 0; i < str.Length; i++) 9 { 10 while (Interlocked.Read(ref used) == 1) 11 { 12 Thread.Sleep(50); 13 } 14 buffer = str[i]; 15 Interlocked.Increment(ref used); 16 } 17 18 }); 19 Thread thReader = new Thread(delegate () 20 { 21 for (int i = 0; i < str.Length; i++) 22 { 23 while (Interlocked.Read(ref used) == 0) 24 { 25 Thread.Sleep(50); 26 } 27 char ch = buffer; 28 Console.Write(ch); 29 Interlocked.Decrement(ref used); 30 } 31 }); 32 thWriter.Start(); 33 thReader.Start(); 34 Console.ReadKey(); 35 }
显示效果:文字会一个一个的打印出来,并且不会乱。
AutoResetEvent ManualResetEvent
实现线程通信,类似信号量Semaphore
Set() 将信号状态设置为有信号
Reset() 将信号状态设置为无信号
WaitOne() 无信号时线程阻塞,有信号时线程无阻塞
AutoResetEvent和ManualResetEvent的区别在于WaitOne方法,AutoResetEvent的WaitOne会自动改变事件对象的状态,每次只能唤醒一个线程。