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