并发编程 - 线程同步(七)之互斥锁Monitor

news/2025/2/13 15:58:29/文章来源:https://www.cnblogs.com/hugogoos/p/18713781

通过前面对锁lock的基本使用以及注意事项的学习,相信大家对锁的同步机制有了大致了解,今天我们将继续学习——互斥锁Monitor。

lock是C#语言中的关键字,是语法糖,lock语句最终会由C#编译器解析成Monitor类实现相关语句。

例如以下lock语句:

lock (obj)
{//同步代码块
}

最终会被解析成以下代码:

Monitor.Enter(obj);
try
{//同步代码块
}
finally
{Monitor.Exit(obj);
}

lock关键字简洁且易于使用,而Monitor类 则功能强大,能够提供比lock关键字更细粒度、更灵活的控制以及更多的功能。

因为lock关键字是Monitor类的语法糖,因此lock关键字面临的问题,Monitor类同样也会面临。当然也会存在一些Monitor类特有的问题。

下面我们一起详细学习Monitor类的注意事项以及实现一个简单的生产者-消费者模式示例代码。

01、避免锁定值类型

这是因为 Monitor.Enter方法的参数为Object类型,这就导致如果传递值类型会导致值类型被装箱,进而导致线程在已装箱的对象上获取锁,最终线程每次调用Monitor.Enter方法都在一个完全不同的对象上获取锁,导致锁失效,无法实现线程同步。

看看下面这个代码示例:

public class LockValueTypeExample
{private static readonly int _lock = 88;public void Method1(){try{Monitor.Enter(_lock);var threadId = Thread.CurrentThread.ManagedThreadId;Console.WriteLine($"线程 {threadId} 通过 lock(值类型) 锁进入 Method1");Console.WriteLine($"进入时间 {DateTime.Now:HH:mm:ss}");Console.WriteLine($"开始休眠 5 秒");Console.WriteLine($"------------------------------------");Thread.Sleep(5000);}finally{Console.WriteLine($"开始释放锁 {DateTime.Now:HH:mm:ss}");Monitor.Exit(_lock);Console.WriteLine($"完成锁释放 {DateTime.Now:HH:mm:ss}");}}
}
public static void LockValueTypeRun()
{var example = new LockValueTypeExample();var thread1 = new Thread(example.Method1);thread1.Start();
}

看看执行结果:

可以发现在释放锁的时候抛出异常,大致意思是:“对象同步方法在未同步的代码块中被调用。”,这就是因为锁定的地方和释放的地方锁已经不一样了。

02、小心try/finally

如上面的例子,Monitor.Enter方法是写在try块中,试想一下:如果在Monitor.Enter方法之前抛出了异常会怎样异常?看下面这段代码:

public class LockBeforeExceptionExample
{private static readonly object _lock = new object();public void Method1(){try{if (new Random().Next(2) == 1){Console.WriteLine($"在调用Monitor.Enter前发生异常");throw new Exception("在调用Monitor.Enter前发生异常");}Monitor.Enter(_lock);}catch (Exception ex){Console.WriteLine($"捕捉到异常:{ex.Message}");}finally{Console.WriteLine($"开始释放锁 {DateTime.Now:HH:mm:ss}");Monitor.Exit(_lock);Console.WriteLine($"完成锁释放 {DateTime.Now:HH:mm:ss}");}}
}
public static void LockBeforeExceptionRun()
{var example = new LockBeforeExceptionExample();var thread1 = new Thread(example.Method1);thread1.Start();
}

上面代码是在调用Monitor.Enter方法前随机抛出异常,当发生异常后,可以在释放锁的时候和锁定值类型报了同样的错误,执行结果如下:

这是因为还没有执行锁定就抛出异常,导致释放一个没有锁定的锁。

那要如何解决这个问题呢?Monitor类已经考虑到了这种情况,并给出了解决办法——使用Monitor.Enter的第二个参数lockTaken,当获取锁定成功则更改lockTaken为true。如此在finally的时候只需要判断lockTaken即可决定是否需要执行释放锁操作,具体代码如下:

public class LockSolveBeforeExceptionExample
{private static readonly object _lock = new object();public void Method1(){var lockTaken = false;try{if (new Random().Next(2) == 1){Console.WriteLine($"在调用Monitor.Enter前发生异常");throw new Exception("在调用Monitor.Enter前发生异常");}Monitor.Enter(_lock,ref lockTaken);}catch (Exception ex){Console.WriteLine($"捕捉到异常:{ex.Message}");}finally{if (lockTaken){Console.WriteLine($"开始释放锁 {DateTime.Now:HH:mm:ss}");Monitor.Exit(_lock);Console.WriteLine($"完成锁释放 {DateTime.Now:HH:mm:ss}");}else{Console.WriteLine($"未执行锁定,无需释放锁");}}}
}
public static void LockSolveBeforeExceptionRun()
{var example = new LockSolveBeforeExceptionExample();var thread1 = new Thread(example.Method1);thread1.Start();
}

执行结果如下:

03、善用TryEnter

我们知道使用锁应当避免长时间持有锁,长时间持有锁会阻塞其他线程,影响性能。我们可以通过Monitor.TryEnter指定超时时间,可以看看下面示例代码:

public class LockTryEnterExample
{private static readonly object _lock = new object();public void Method1(){try{Monitor.Enter(_lock);Console.WriteLine($"Method1 | 获取锁成功,并锁定 5 秒");Thread.Sleep(5000);}finally{Monitor.Exit(_lock);}}public void Method2(){Console.WriteLine($"Method2 | 尝试获取锁");if (Monitor.TryEnter(_lock, 3000)){try{}finally{}}else{Console.WriteLine($"Method2 | 3 秒内未获取到锁,自动退出锁");}}public void Method3(){Console.WriteLine($"Method3 | 尝试获取锁");if (Monitor.TryEnter(_lock, 7000)){try{Console.WriteLine($"Method3 | 7 秒内获取到锁");}finally{Console.WriteLine($"Method3 |开始释放锁");Monitor.Exit(_lock);Console.WriteLine($"Method3 |完成锁释放");}}else{Console.WriteLine($"Method3 | 7 秒内未获取到锁,自动退出锁");}}
}
public static void LockTryEnterRun()
{var example = new LockTryEnterExample();var thread1 = new Thread(example.Method1);var thread2 = new Thread(example.Method2);var thread3 = new Thread(example.Method3);thread1.Start();thread2.Start();thread3.Start();
}

执行结果如下:

可以发现当Method1锁定5秒后,Method2尝试3秒内获取锁,结果并未获取到自动退出;然后Method3尝试7秒内获取锁,结果获取到锁并正确释放锁。

04、实现生产者-消费者模式

除了上面介绍的方法,Monitor类还有Wait、Pulse、PulseAll等方法。

Wait: 该方法用于将当前线程放入等待队列,直到收到其他线程的信号通知。

Pulse: 该方法用于唤醒等待队列中的一个线程。当一个线程调用 Pulse 时,它会通知一个正在等待该对象锁的线程继续执行。

PulseAll: 该方法用于唤醒等待队列中的所有线程。

然后我们利用Monitor类的这些功能来实现一个简单的生产者-消费者模式。大致思路如下:

1.首先启动生产者线程,获取锁,然后生成数据;

2.当生产者生产的数据小于数据队列长度,则生产一条数据同时通知消费者线程进行消费,否则暂停当前线程等待消费者线程消费数据;

3.然后启动消费者线程,获取锁,然后消费数据;

4.当数据队列中有数据,则消费一条数据同时通知生产者线程可以生产数据了,否则暂停当前线程等待生产者线程生产数据;

具体代码如下:

public class LockProducerConsumerExample
{private static Queue<int> queue = new Queue<int>();private static object _lock = new object();//生产者public  void Producer(){while (true){lock (_lock){Console.ForegroundColor = ConsoleColor.Red;if (queue.Count < 3){var item = new Random().Next(100);queue.Enqueue(item);Console.WriteLine($"生产者,生产: {item}");//唤醒消费者Monitor.Pulse(_lock);  }else{//队列满时,生产者等待Console.WriteLine($"队列已满,生产者等待中……");Monitor.Wait(_lock);  }}Thread.Sleep(500);}}// 消费者public  void Consumer(){while (true){lock (_lock){Console.ForegroundColor = ConsoleColor.Blue;if (queue.Count > 0){var item = queue.Dequeue();Console.WriteLine($"消费者,消费: {item}");//唤醒生产者Monitor.Pulse(_lock);  }else{//队列空时,消费者等待Console.WriteLine($"队列已空,消费者等待中……");Monitor.Wait(_lock);  }}Thread.Sleep(10000);}}
}
public static void LockProducerConsumerRun()
{var example = new LockProducerConsumerExample();var thread1 = new Thread(example.Producer);var thread2 = new Thread(example.Consumer);thread1.Start();thread2.Start();thread1.Join();thread2.Join();
}

执行结果如下:

:测试方法代码以及示例源码都已经上传至代码库,有兴趣的可以看看。https://gitee.com/hugogoos/Planner

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.hqwc.cn/news/883261.html

如若内容造成侵权/违法违规/事实不符,请联系编程知识网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

BI大屏自适应的构建

对于大屏自适应,网上有基于Vue编写的大屏自适应逻辑代码,本文讲解如何在一个初始化大屏的页面中使用自适应逻辑代码,以及如何解决自适应后大屏左右两边留白的问题。 首先,在编写的初始化html文件中,需要引入离线版的vue JS包和大屏自适应的js代码:1 <script src="…

SQLserver开启CDC功能

1.查询数据库是否已经开启CDC功能 select is_cdc_enabled,name from sys.databases where name=databasename; 开启为1,未开启为02.库级别开启CDC功能 use databasename GO exec sys.sp_cdc_enable_db 3.再次确认CDC功能是否开启 select is_cdc_enabled,name from sys.databas…

N1Junior

N1JuniorGavatar 核心逻辑如下: upload.php <?php require_once common.php;$user = getCurrentUser(); if (!$user) header(Location: index.php);$avatarDir = __DIR__ . /avatars; if (!is_dir($avatarDir)) mkdir($avatarDir, 0755);$avatarPath = "$avatarDir/{$…

软件自启动取消权限弹窗询问设置

方案1:创建任务计划--启动软件取消UAC权限询问弹窗 1.1 打开任务计划 右键“我的电脑”--“管理”,打开任务计划,如图所示1.2 创建任务计划1.2.1 常规参数设置1.2.2 触发器设置1.2.3 操作设置最后按确定即可完成任务计划创建,至此电脑重启登陆后,软件都会自动启动 方案2:…

hgame 第一周

hgame 第一周Pacman 前端反调试题,找到了一个串.aGFldTRlcGNhXzR0cmdte19yX2Ftbm1zZX0=.base64解码后百思不得其解,后来发现还有一个栅栏加密,纯烂活... hgame{u_4re_pacman_m4ster} BandBomb const express = require(express); const multer = require(multer); const fs = r…

Deepseek学习随笔(2)--- 快速上手DeepSeek

注册与登录 要开始使用 DeepSeek,你需要先注册一个账号。以下是具体步骤:访问 DeepSeek 官网。 使用邮箱或手机号注册账号。 登录后进入控制台,开始使用。控制台功能介绍 DeepSeek 的控制台提供了丰富的功能,帮助你更好地使用工具:对话窗口:在这里输入你的问题或指令,De…

【原创】大数据治理入门(4)《保护数据隐私:大数据治理的最佳实践》入门必看 高赞实用

在大数据治理中,保护数据隐私是至关重要的。通过了解和遵守相关法规,采用数据脱敏、匿名化和隐私计算等技术手段,企业可以有效降低数据隐私泄露的风险,提升用户的信任度和企业的竞争力。希望本文能够为您提供全面的数据隐私保护指南。保护数据隐私:大数据治理的最佳实践 引…

【原创】大数据治理入门(1)《大数据治理入门:为什么重要?》入门必看 高赞实用

随着互联网的快速发展,数据的生成和积累速度达到了前所未有的水平。大数据通常被定义为规模巨大、类型多样且生成速度快的数据集合。这些数据不仅包括结构化数据(如数据库中的表格数据),还包括非结构化数据(如文本、图片、视频等)。大数据的重要性在于其能够帮助企业更好…

【MySQL安全】复现Mysql LOAD DATA 读取客户端任意文件漏洞

前言 MySQL 客户端和服务端通信过程中是通过对话的形式来实现的,客户端发送一个操作请求,然后服务端根据客户端发送的请求来响应客户端,在这个过程中客户端如果一个操作需要两步才能完成,那么当它发送完第一个请求过后并不会存储这个请求,而是直接丢弃,所以第二步就是根据…

线程/进程

进程 1.是资源分配的基本单元 2.拥有独立的内存空间和资源 3.开销较大 4.稳定性高,一个进程的崩溃不会影响其他的进程 线程 1.是cpu调度的基本单元 2.共享进程的资源 3.开销较小 4.稳定性不高,一个线程崩溃可能会导致其他线程的崩溃当我们写完一个线程之后可以通过idea的安装…

nginx代理grafana配置

配置grafna的nginx代理配置环境背景:监控系统用的是Prometheus+altermanager+grafana+xxx-exporter实现的,现在业界内的夜莺监控系统已经非常的成熟了,使用夜莺管理配置监控相对比Prometheus+altermanager要友好很多,使用也方便,直接页面配置就好。但是相对图表化的监控信…

IPD流程管理之市场需求精准把握方法

IPD(Integrated Product Development)流程管理旨在通过整合产品开发的各个环节,实现高效、高质量的产品交付。在这一过程中,精准把握市场需求是成功的关键。只有深入了解市场需求,企业才能开发出符合客户期望的产品,从而在激烈的市场竞争中占据优势。本文将详细探讨IPD流…