1.Lock
class Program { //资源 static object obj = new object(); static int count = 0; static void Main(string[] args) { for (int i = 0; i < 10; i++) { lock(obj) { Thread t = new Thread(Run); t.Start(); } } Console.Read(); } static void Run() { Thread.Sleep(10); Console.WriteLine("当前数字:{0}", ++count); } }
2.Monitor类
这个算是实现锁机制的纯正类,在锁定的临界区中只允许让一个线程访问,其他线程排队等待。主要整理为2组方法。
2.1Monitor.Enter和Monitor.Exit
微软很照护我们,给了我们语法糖Lock,对的,语言糖确实减少了我们不必要的劳动并且让代码更可观,但是如果我们要精细的控制,则必须使用原生类,这里要注意一个问题就是“锁住什么”的问题,一般情况下我们锁住的都是静态对象,我们知道静态对象属于类级别,当有很多线程共同访问的时候,那个静态对象对多个线程来说是一个,不像实例字段会被认为是多个。
class Program { //资源 static object obj = new object(); static int count = 0; static void Main(string[] args) { for (int i = 0; i < 10; i++) { Thread t = new Thread(Run); t.Start(); } Console.Read(); } static void Run() { Thread.Sleep(10); //进入临界区 Monitor.Enter(obj); Console.WriteLine("当前数字:{0}", ++count); //退出临界区 Monitor.Exit(obj); } }
2.Monitor.Wait和Monitor.Pulse
首先这两个方法是成对出现,通常使用在Enter,Exit之间。
Wait:暂时的释放资源锁,然后该线程进入”等待队列“中,那么自然别的线程就能获取到资源锁。
Pulse: 唤醒“等待队列”中的线程,那么当时被Wait的线程就重新获取到了锁。
这里我们是否注意到了两点:
1.可能A线程进入到临界区后,需要B线程做一些初始化操作,然后A线程继续干剩下的事情。
2.用上面的两个方法,我们可以实现线程间的彼此通信。
public class Program { public static void Main(string[] args) { LockObj obj = new LockObj(); //注意,这里使用的是同一个资源对象obj Jack jack = new Jack(obj); John john = new John(obj); Thread t1 = new Thread(new ThreadStart(jack.Run)); Thread t2 = new Thread(new ThreadStart(john.Run)); t1.Start(); t1.Name = "Jack"; t2.Start(); t2.Name = "John"; Console.ReadLine(); } } //锁定对象 public class LockObj { } public class Jack { private LockObj obj; public Jack(LockObj obj) { this.obj = obj; } public void Run() { Monitor.Enter(this.obj); Console.WriteLine("{0}:111", Thread.CurrentThread.Name); //暂时的释放锁资源 Monitor.Wait(this.obj); Console.WriteLine("{0}:222", Thread.CurrentThread.Name); //唤醒等待队列中的线程 Monitor.Pulse(this.obj); Console.WriteLine("{0}:333", Thread.CurrentThread.Name); Monitor.Exit(this.obj); } } public class John { private LockObj obj; public John(LockObj obj) { this.obj = obj; } public void Run() { Monitor.Enter(this.obj); Console.WriteLine("{0}:111",Thread.CurrentThread.Name); //唤醒等待队列中的线程 Monitor.Pulse(this.obj); Console.WriteLine("{0}:222", Thread.CurrentThread.Name); //暂时的释放锁资源 Monitor.Wait(this.obj); Console.WriteLine("{0}:333", Thread.CurrentThread.Name); Monitor.Exit(this.obj); } }
3.ReaderWriterLock类
先前也知道,Monitor实现的是在读写两种情况的临界区中只可以让一个线程访问,那么如果业务中存在”读取密集型“操作,就好比数据库一样,读取的操作永远比写入的操作多。针对这种情况,我们使用Monitor的话很吃亏,不过没关系,ReadWriterLock就很牛X,因为实现了”写入串行“,”读取并行“。
ReaderWriteLock中主要用3组方法:
1.AcquireWriterLock: 获取写入锁。
ReleaseWriterLock:释放写入锁。
2.AcquireReaderLock: 获取读锁。
ReleaseReaderLock:释放读锁。
3.UpgradeToWriterLock:将读锁转为写锁。
DowngradeFromWriterLock:将写锁还原为读锁。
class Program { static List<int> list = new List<int>(); static ReaderWriterLock rw = new System.Threading.ReaderWriterLock(); static void Main(string[] args) { Thread t1 = new Thread(AutoAddFunc); Thread t2 = new Thread(AutoReadFunc); t1.Start(); t2.Start(); Console.Read(); } /// <summary> /// 模拟3s插入一次 /// </summary> /// <param name="num"></param> public static void AutoAddFunc() { //3000ms插入一次 Timer timer1 = new Timer(new TimerCallback(Add), null, 0, 3000); } public static void AutoReadFunc() { //1000ms自动读取一次 Timer timer1 = new Timer(new TimerCallback(Read), null, 0, 1000); Timer timer2 = new Timer(new TimerCallback(Read), null, 0, 1000); Timer timer3 = new Timer(new TimerCallback(Read), null, 0, 1000); } public static void Add(object obj) { var num = new Random().Next(0, 1000); //写锁 rw.AcquireWriterLock(TimeSpan.FromSeconds(30)); list.Add(num); Console.WriteLine("我是线程{0},我插入的数据是{1}。", Thread.CurrentThread.ManagedThreadId, num); //释放锁 rw.ReleaseWriterLock(); } public static void Read(object obj) { //读锁 rw.AcquireReaderLock(TimeSpan.FromSeconds(30)); Console.WriteLine("我是线程{0},我读取的集合为:{1}", Thread.CurrentThread.ManagedThreadId, string.Join(",", list)); //释放锁 rw.ReleaseReaderLock(); } }