博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
5天不再惧怕多线程——第二天 锁机制
阅读量:5896 次
发布时间:2019-06-19

本文共 6699 字,大约阅读时间需要 22 分钟。

 

     当多个线程在并发的时候,难免会碰到相互冲突的事情,比如最经典的ATM机的问题,并发不可怕,可怕的是我们没有能力控制。

线程以我的理解可以分为三种

① 锁。

② 互斥。

③ 信号。

  好,这一篇主要整理“锁”,C#提供了2种手工控制的锁

一:  Monitor类

     这个算是实现锁机制的纯正类,在锁定的临界区中只允许让一个线程访问,其他线程排队等待。主要整理为2组方法。

 

1:Monitor.Enter和Monitor.Exit

         微软很照护我们,给了我们语法糖Lock,对的,语言糖确实减少了我们不必要的劳动并且让代码更可观,但是如果我们要精细的

     控制,则必须使用原生类,这里要注意一个问题就是“锁住什么”的问题,一般情况下我们锁住的都是静态对象,我们知道静态对象

     属于类级别,当有很多线程共同访问的时候,那个静态对象对多个线程来说是一个,不像实例字段会被认为是多个。

 

不加锁的情况:

1    class Program  2     {
3 static void Main(string[] args) 4 {
5 for (int i = 0; i < 10; i++) 6 {
7 Thread t = new Thread(Run); 8 9 t.Start(); 10 } 11 } 12 13 //资源 14 static object obj = new object(); 15 16 static int count = 0; 17 18 static void Run() 19 {
20 Thread.Sleep(10); 21 22 Console.WriteLine("当前数字:{0}", ++count); 23 } 24 }

 

加锁的情况:

1     class Program  2     {
3 static void Main(string[] args) 4 {
5 for (int i = 0; i < 10; i++) 6 {
7 Thread t = new Thread(Run); 8 9 t.Start(); 10 } 11 } 12 13 //资源 14 static object obj = new object(); 15 16 static int count = 0; 17 18 static void Run() 19 {
20 Thread.Sleep(10); 21 22 //进入临界区 23 Monitor.Enter(obj); 24 25 Console.WriteLine("当前数字:{0}", ++count); 26 27 //退出临界区 28 Monitor.Exit(obj); 29 } 30 }

 

2:Monitor.Wait和Monitor.Pulse

 首先这两个方法是成对出现,通常使用在Enter,Exit之间。

 Wait: 暂时的释放资源锁,然后该线程进入”等待队列“中,那么自然别的线程就能获取到资源锁。

 Pulse:  唤醒“等待队列”中的线程,那么当时被Wait的线程就重新获取到了锁。

 

这里我们是否注意到了两点:

①   可能A线程进入到临界区后,需要B线程做一些初始化操作,然后A线程继续干剩下的事情。

②   用上面的两个方法,我们可以实现线程间的彼此通信。

 

下面举个例子来模拟两个人的对话。

1 using System;  2 using System.Collections.Generic;  3 using System.Text;  4 using System.Threading;  5  6 namespace Test  7 {
8 public class Program 9 {
10 public static void Main(string[] args) 11 {
12 LockObj obj = new LockObj(); 13 14 //注意,这里使用的是同一个资源对象obj 15 Jack jack = new Jack(obj); 16 John john = new John(obj); 17 18 Thread t1 = new Thread(new ThreadStart(jack.Run)); 19 Thread t2 = new Thread(new ThreadStart(john.Run)); 20 21 t1.Start(); 22 t1.Name = "Jack"; 23 24 t2.Start(); 25 t2.Name = "John"; 26 27 Console.ReadLine(); 28 } 29 } 30 31 //锁定对象 32 public class LockObj { } 33 34 public class Jack 35 {
36 private LockObj obj; 37 38 public Jack(LockObj obj) 39 {
40 this.obj = obj; 41 } 42 43 public void Run() 44 {
45 Monitor.Enter(this.obj); 46 47 Console.WriteLine("{0}:我已进入茅厕。", Thread.CurrentThread.Name); 48 49 Console.WriteLine("{0}:擦,太臭了,我还是撤!", Thread.CurrentThread.Name); 50 51 //暂时的释放锁资源 52 Monitor.Wait(this.obj); 53 54 Console.WriteLine("{0}:兄弟说的对,我还是进去吧。", Thread.CurrentThread.Name); 55 56 //唤醒等待队列中的线程 57 Monitor.Pulse(this.obj); 58 59 Console.WriteLine("{0}:拉完了,真舒服。", Thread.CurrentThread.Name); 60 61 Monitor.Exit(this.obj); 62 } 63 } 64 65 public class John 66 {
67 private LockObj obj; 68 69 public John(LockObj obj) 70 {
71 this.obj = obj; 72 } 73 74 public void Run() 75 {
76 Monitor.Enter(this.obj); 77 78 Console.WriteLine("{0}:直奔茅厕,兄弟,你还是进来吧,小心憋坏了!", 79 Thread.CurrentThread.Name); 80 81 //唤醒等待队列中的线程 82 Monitor.Pulse(this.obj); 83 84 Console.WriteLine("{0}:哗啦啦....", Thread.CurrentThread.Name); 85 86 //暂时的释放锁资源 87 Monitor.Wait(this.obj); 88 89 Console.WriteLine("{0}:拉完了,真舒服。", Thread.CurrentThread.Name); 90 91 Monitor.Exit(this.obj); 92 } 93 } 94 }

 

二:ReaderWriterLock类

    先前也知道,Monitor实现的是在读写两种情况的临界区中只可以让一个线程访问,那么如果业务中存在”读取密集型“操作,就

好比数据库一样,读取的操作永远比写入的操作多。针对这种情况,我们使用Monitor的话很吃亏,不过没关系,ReadWriterLock

就很牛X,因为实现了”写入串行“,”读取并行“。

ReaderWriteLock中主要用3组方法:

<1>  AcquireWriterLock: 获取写入锁。

          ReleaseWriterLock:释放写入锁。

<2>  AcquireReaderLock: 获取读锁。

          ReleaseReaderLock:释放读锁。

<3>  UpgradeToWriterLock:将读锁转为写锁。

         DowngradeFromWriterLock:将写锁还原为读锁。

 

下面就实现一个写操作,三个读操作,要知道这三个读操作是并发的。

1 namespace Test  2 {
3 class Program 4 {
5 static List
list = new List
(); 6 7 static ReaderWriterLock rw = new System.Threading.ReaderWriterLock(); 8 9 static void Main(string[] args) 10 {
11 Thread t1 = new Thread(AutoAddFunc); 12 13 Thread t2 = new Thread(AutoReadFunc); 14 15 t1.Start(); 16 17 t2.Start(); 18 19 Console.Read(); 20 } 21 22 ///
23 /// 模拟3s插入一次 24 /// 25 ///
26 public static void AutoAddFunc() 27 {
28 //3000ms插入一次 29 Timer timer1 = new Timer(new TimerCallback(Add), null, 0, 3000); 30 } 31 32 public static void AutoReadFunc() 33 {
34 //1000ms自动读取一次 35 Timer timer1 = new Timer(new TimerCallback(Read), null, 0, 1000); 36 Timer timer2 = new Timer(new TimerCallback(Read), null, 0, 1000); 37 Timer timer3 = new Timer(new TimerCallback(Read), null, 0, 1000); 38 } 39 40 public static void Add(object obj) 41 {
42 var num = new Random().Next(0, 1000); 43 44 //写锁 45 rw.AcquireWriterLock(TimeSpan.FromSeconds(30)); 46 47 list.Add(num); 48 49 Console.WriteLine("我是线程{0},我插入的数据是{1}。", Thread.CurrentThread.ManagedThreadId, num); 50 51 //释放锁 52 rw.ReleaseWriterLock(); 53 } 54 55 public static void Read(object obj) 56 {
57 //读锁 58 rw.AcquireReaderLock(TimeSpan.FromSeconds(30)); 59 60 Console.WriteLine("我是线程{0},我读取的集合为:{1}", 61 Thread.CurrentThread.ManagedThreadId, string.Join(",", list)); 62 //释放锁 63 rw.ReleaseReaderLock(); 64 } 65 } 66 }

转载地址:http://chxsx.baihongyu.com/

你可能感兴趣的文章
PHP队列的实现
查看>>
单点登录加验证码例子
查看>>
[T-SQL]从变量与数据类型说起
查看>>
occActiveX - ActiveX with OpenCASCADE
查看>>
BeanUtils\DBUtils
查看>>
python模块--os模块
查看>>
linux下单节点oracle数据库间ogg搭建
查看>>
Java 数组在内存中的结构
查看>>
《关爱码农成长计划》第一期报告
查看>>
学习进度表 04
查看>>
谈谈javascript中的prototype与继承
查看>>
时序约束优先级_Vivado工程经验与各种时序约束技巧分享
查看>>
minio 并发数_MinIO 参数解析与限制
查看>>
flash back mysql_mysqlbinlog flashback 使用最佳实践
查看>>
mysql存储引擎模式_MySQL存储引擎
查看>>
python类 del_全面了解Python类的内置方法
查看>>
java jni 原理_使用JNI技术实现Java和C++的交互
查看>>
java 重写system.out_重写System.out.println(String x)方法
查看>>
配置ORACLE 11g绿色版客户端和PLSQL远程连接环境
查看>>
ASP.NET中 DataList(数据列表)的使用前台绑定
查看>>