线程锁机制

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

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

① 锁。

② 互斥。

③ 信号。

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

一:  Monitor类

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

1:Monitor.Enter和Monitor.Exit

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

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

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

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

加锁的情况:

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

二: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);
        }

        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();
        }
    }

  

原文地址:https://www.cnblogs.com/hbsfgl/p/4896468.html