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

相关文章
|
3月前
|
安全 Java 调度
Java编程时多线程操作单核服务器可以不加锁吗?
Java编程时多线程操作单核服务器可以不加锁吗?
47 2
|
16天前
|
供应链 安全 NoSQL
PHP 互斥锁:如何确保代码的线程安全?
在多线程和高并发环境中,确保代码段互斥执行至关重要。本文介绍了 PHP 互斥锁库 `wise-locksmith`,它提供多种锁机制(如文件锁、分布式锁等),有效解决线程安全问题,特别适用于电商平台库存管理等场景。通过 Composer 安装后,开发者可以利用该库确保在高并发下数据的一致性和安全性。
30 6
|
16天前
|
存储 监控 安全
深入理解ThreadLocal:线程局部变量的机制与应用
在Java的多线程编程中,`ThreadLocal`变量提供了一种线程安全的解决方案,允许每个线程拥有自己的变量副本,从而避免了线程间的数据竞争。本文将深入探讨`ThreadLocal`的工作原理、使用方法以及在实际开发中的应用场景。
29 2
|
1月前
|
Java
线程池内部机制:线程的保活与回收策略
【10月更文挑战第24天】 线程池是现代并发编程中管理线程资源的一种高效机制。它不仅能够复用线程,减少创建和销毁线程的开销,还能有效控制并发线程的数量,提高系统资源的利用率。本文将深入探讨线程池中线程的保活和回收机制,帮助你更好地理解和使用线程池。
69 2
|
2月前
|
Java
在Java多线程编程中,`wait()` 和 `notify()/notifyAll()` 方法是线程间通信的核心机制。
在Java多线程编程中,`wait()` 和 `notify()/notifyAll()` 方法是线程间通信的核心机制。它们通过基于锁的方式,使线程在条件不满足时进入休眠状态,并在条件成立时被唤醒,从而有效解决数据一致性和同步问题。本文通过对比其他通信机制,展示了 `wait()` 和 `notify()` 的优势,并通过生产者-消费者模型的示例代码,详细说明了其使用方法和重要性。
28 1
|
2月前
|
安全 Java 开发者
在多线程编程中,确保数据一致性与防止竞态条件至关重要。Java提供了多种线程同步机制
【10月更文挑战第3天】在多线程编程中,确保数据一致性与防止竞态条件至关重要。Java提供了多种线程同步机制,如`synchronized`关键字、`Lock`接口及其实现类(如`ReentrantLock`),还有原子变量(如`AtomicInteger`)。这些工具可以帮助开发者避免数据不一致、死锁和活锁等问题。通过合理选择和使用这些机制,可以有效管理并发,确保程序稳定运行。例如,`synchronized`可确保同一时间只有一个线程访问共享资源;`Lock`提供更灵活的锁定方式;原子变量则利用硬件指令实现无锁操作。
27 2
|
2月前
|
运维 API 计算机视觉
深度解密协程锁、信号量以及线程锁的实现原理
深度解密协程锁、信号量以及线程锁的实现原理
43 1
|
2月前
|
Java 应用服务中间件 测试技术
Java21虚拟线程:我的锁去哪儿了?
【10月更文挑战第8天】
43 0
|
2月前
|
安全 调度 数据安全/隐私保护
iOS线程锁
iOS线程锁
30 0
|
2月前
|
Java API
【多线程】乐观/悲观锁、重量级/轻量级锁、挂起等待/自旋锁、公平/非公锁、可重入/不可重入锁、读写锁
【多线程】乐观/悲观锁、重量级/轻量级锁、挂起等待/自旋锁、公平/非公锁、可重入/不可重入锁、读写锁
38 0