5天不再惧怕多线程——第二天 锁机制

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

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

① 锁。

② 互斥。

③ 信号。

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

一:  Monitor类

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

 

1:Monitor.Enter和Monitor.Exit

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

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

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

 

不加锁的情况:

class Program
    {
        static void Main(string[] args)
        {
            for (int i = 0; i < 10; i++)
            {
                Thread t = new Thread(Run);

                t.Start();
            }
        }

        //资源
        static object obj = new object();

        static int count = 0;

        static void Run()
        {
            Thread.Sleep(10);

            Console.WriteLine("当前数字:{0}", ++count);
        }
    }

 

加锁的情况:

class Program
    {
        static void Main(string[] args)
        {
            for (int i = 0; i < 10; i++)
            {
                Thread t = new Thread(Run);

                t.Start();
            }
        }

        //资源
        static object obj = new object();

        static int count = 0;

        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的线程就重新获取到了锁。

 

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

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

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

 

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

using System;
using System.Collections.Generic;
using System.Text;
using System.Threading;

namespace Test
{
    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}:我已进入茅厕。", Thread.CurrentThread.Name);

            Console.WriteLine("{0}:擦,太臭了,我还是撤!", Thread.CurrentThread.Name);

            //暂时的释放锁资源
            Monitor.Wait(this.obj);

            Console.WriteLine("{0}:兄弟说的对,我还是进去吧。", Thread.CurrentThread.Name);

            //唤醒等待队列中的线程
            Monitor.Pulse(this.obj);

            Console.WriteLine("{0}:拉完了,真舒服。", 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}:直奔茅厕,兄弟,你还是进来吧,小心憋坏了!",
                               Thread.CurrentThread.Name);

            //唤醒等待队列中的线程
            Monitor.Pulse(this.obj);

            Console.WriteLine("{0}:哗啦啦....", Thread.CurrentThread.Name);

            //暂时的释放锁资源
            Monitor.Wait(this.obj);

            Console.WriteLine("{0}:拉完了,真舒服。", Thread.CurrentThread.Name);

            Monitor.Exit(this.obj);
        }
    }
}

 

二:ReaderWriterLock类

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

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

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

ReaderWriteLock中主要用3组方法:

<1>  AcquireWriterLock: 获取写入锁。

          ReleaseWriterLock:释放写入锁。

<2>  AcquireReaderLock: 获取读锁。

          ReleaseReaderLock:释放读锁。

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

         DowngradeFromWriterLock:将写锁还原为读锁。

 

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

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

时间: 2024-10-09 19:53:17

5天不再惧怕多线程——第二天 锁机制的相关文章

5天不再惧怕多线程——第三天 互斥体

     没想到我的前两篇文章还挺受欢迎的,谢谢大家,今天整理下Mutex的使用. 一:Mutex 首先看下MSDN对它的解释:      不错,出现了一个亮点,可用于"进程间同步",既然进程间都可以同步,那线程同步对它来说不是小菜一碟吗?好的,还是看下Mutex在 线程中发挥的神奇功效. 1: 线程间同步     Metux中提供了WatiOne和ReleaseMutex来确保只有一个线程来访问共享资源,是不是跟Monitor很类似,下面我还是举个简单的例子, 注意我并没有给Metu

5天不再惧怕多线程——第五天 线程池

说到多线程,不可不说线程池,C#中关于池的概念很多,今天来整理下ThreadPool的使用. 是的,如果你很懒,如果你的执行任务比较短,如果你不想对线程做更精细的控制,那么把这些繁琐的东西丢给线程池吧. 一:ThreadPool 好了,下面看看TheadPool下有哪些常用的方法. 1:GetMaxThreads,GetMinThreads 首先我们肯定好奇线程池到底给我们如何控制线程数,下面就具体的看一看. class Program { static void Main(string[] a

5天不再惧怕多线程——第一天 尝试Thread

     原本准备在mongodb之后写一个lucene.net系列,不过这几天用到多线程时才发现自己对多线程的了解少之又少,仅仅停留在lock上面, 故这几天看了下线程参考手册结合自己的心得整理一下放在博客上作为自己的学习笔记.      好了,我们知道"负载"是一个很时尚,很牛X的玩意,往大处说,网站需要负载,数据库需要负载.往小处说,线程也需要负载,面对海量的 用户请求,我们的单线程肯定扛不住,那么怎么办,一定要负载,所以说多线程是我们码农必须要熟练掌握的一门技术.     在f

5天不再惧怕多线程——第四天 信号量

    今天整理"信号量"的相关知识,其实想想也蛮有趣的,锁,互斥,信号量都可以实现线程同步,在framework里面主要有三种. <1>:ManualResetEvent <2>:AutoResetEvent <3>: Semaphore   好,下面就具体看看这些玩意的使用.   一:ManualResetEvent       该对象有两种信号量状态True和False,好奇的我们肯定想知道True和False有什么区别,稍后的例子见分晓,有三

Java多线程的同步示例及对象锁机制

java多线程的同步依靠的是对象锁机制,synchronized关键字的背后就是利用了封锁来实现对共享资源的互斥访问. 下面以一个简单的实例来进行对比分析.实例要完成的工作非常简单,就是创建10个线程,每个线程都打印从0到99这100个数字,我们希望线程之间不会出现交叉乱序打印,而是顺序地打印. 先来看第一段代码,这里我们在run()方法中加入了synchronized关键字,希望能对run方法进行互斥访问,但结果并不如我们希望那样,这 是因为这里synchronized锁住的是this对象,即

多线程中递归锁的实现.

在上一篇文章中,我已经阐述了多线程中简单锁的实现,可在结束的时候,我就提了那么一个问题,那就是如果在一个链表中进行插入时,要进行查询的操作,如果只是简单的锁,是没法实现的.所以"递归锁"就浮现于世了. 可能有些人看到递归这两个字,有点傻了眼,其实也没什么的,简单的介绍,就是进行简单的计数而已.刚开始引用锁的时候,就产生它,当在锁没有解开的时候,还要继续用锁,就简单的加一,解开一把就减一,当计数为零时,就把锁销毁掉.下面用程序来简单的阐述一下,递归锁是怎么实现的: 1.递归锁接口的定义.

李彦宏:让周鸿祎不再惧怕Google

    1999年,北京风入松书店.我翻到<硅谷商战>这本书时,看到作者是李彦宏,就放下了.我不认识李彦宏,不相信一个中国人能对硅谷有什么切身了解,以为这又是一本剪刀加浆糊攒出来的书.2000年冬,北京CD酒吧.我作为<计算机世界>报记者应邀请参加百度年终媒体答谢会.最后一个节目是"杀人"游戏.我第一次玩,轻易举手同意将李彦宏"误杀".李彦宏被"误杀"后告诉我,不应该先将他"杀掉",因为他是会玩的人,一

Java多线程:“JUC锁”04之公平锁(二)

概要 前面一章,我们学习了"公平锁"获取锁的详细流程:这里,我们再来看看"公平锁 "释放锁的过程.内容包括: 参考代码 释放公平锁(基于JDK1.7.0_40) "公平锁"的获取过程请参考"Java多线程系列--"JUC锁"03之 公平锁 (一)",锁的使用示例请参考"Java多线程系列--"JUC锁"02之 互斥锁 ReentrantLock". 注意: (01)

Java多线程:“JUC锁”02之互斥锁ReentrantLock

ReentrantLock介绍 ReentrantLock是一个可重入的互斥锁,又被称为"独占锁". 顾名思义,ReentrantLock锁在同一个时间点只能被一个线程锁持有:而可重入的意思是, ReentrantLock锁,可以被单个线程多次获取. ReentrantLock分为"公平锁"和"非 公平锁".它们的区别体现在获取锁的机制上是否公平."锁"是为了保护竞争资源, 防止多个线程同时操作线程而出错,ReentrantL