C#多线程系列(2):多线程锁lock和Monitor
C# 中,可以使用 lock 关键字和 Monitor 类来解决多线程锁定资源和死锁的问题。
官方解释:lock 语句获取给定对象的互斥 lock,执行语句块,然后释放 lock。
下面我们将来探究 lock 关键字和 Monitor 类的使用。
1,Lock
lock 用于读一个引用类型进行加锁,同一时刻内只有一个线程能够访问此对象。lock 是语法糖,是通过 Monitor 来实现的。
Lock 锁定的对象,应该是静态的引用类型(字符串除外)。
如果你能保证字符串的唯一性,例如 Guid 生成的字符串,也是可以作为锁的对象使用的(但不建议)。
锁的对象也不一定要静态才行,也可以通过类实例的成员变量,作为锁对象。
lock 原型
lock 是 Monitor 的语法糖,生成的代码对比:
lock (x)
{
// Your code...
}
object __lockObj = x;
bool __lockWasTaken = false;
try
{
System.Threading.Monitor.Enter(__lockObj, ref __lockWasTaken);
// Your code...
}
finally
{
if (__lockWasTaken) System.Threading.Monitor.Exit(__lockObj);
}
这里先不理会 Monitor,后面再说。
lock 编写实例
首先,如果像下面这样写的话,拉出去打 si 吧。
public void MyLock()
{
object o = new object();
lock (o)
{
//
}
}
下面编写一个简单的锁,示例如下:
class Program
{
private static object obj = new object();
private static int sum = 0;
static void Main(string[] args)
{
Thread thread1 = new Thread(Sum1);
thread1.Start();
Thread thread2 = new Thread(Sum2);
thread2.Start();
while (true)
{
Console.WriteLine($"{DateTime.Now.ToString()}:" + sum);
Thread.Sleep(TimeSpan.FromSeconds(1));
}
}
public static void Sum1()
{
sum = 0;
lock (obj)
{
for (int i = 0; i < 10; i++)
{
sum += i;
Console.WriteLine("Sum1");
Thread.Sleep(TimeSpan.FromSeconds(2));
}
}
}
public static void Sum2()
{
sum = 0;
lock (obj)
{
for (int i = 0; i < 10; i++)
{
sum += 1;
Console.WriteLine("Sum2");
Thread.Sleep(TimeSpan.FromSeconds(2));
}
}
}
}
类将自己设置为锁, 这可以防止恶意代码对公共对象采用做锁。
例如:
public void Access()
{
lock(this) {}
}
锁不太适合I/O场景,例如文件I/O,繁杂的计算或者操作比较持久的过程,会给程序带来很大的性能损失。
10 种优化锁的性能方法: http://www.thinkingparallel.com/2007/07/31/10-ways-to-reduce-lock-contention-in-threaded-programs/
2,Monitor
此对象提供同步访问对象的机制;Monotor 是一个静态类型,其方法比较少,常用方法如下:
操作 | 说明 |
---|---|
Enter, TryEnter | 获取对象的锁。 此操作还标记关键节的开头。 其他任何线程都不能输入临界区,除非它使用不同的锁定对象执行临界区中的说明。 |
Wait | 释放对象的锁,以允许其他线程锁定并访问对象。 调用线程会等待另一个线程访问对象。 使用脉冲信号通知等待线程关于对象状态的更改。 |
Pulse 、PulseAll | 将信号发送到一个或多个等待线程。 信号通知等待线程:锁定对象的状态已更改,锁的所有者已准备好释放该锁。 正在等待的线程置于对象的就绪队列中,因此它可能最终接收对象的锁。 线程锁定后,它可以检查对象的新状态,以查看是否已达到所需的状态。 |
Exit | 释放对象的锁。 此操作还标记受锁定对象保护的临界区的结尾。 |
怎么用呢
下面是一个很简单的示例:
private static object obj = new object();
private static bool acquiredLock = false;
public static void Test()
{
try
{
Monitor.Enter(obj, ref acquiredLock);
}
catch { }
finally
{
if (acquiredLock)
Monitor.Exit(obj);
}
}
Monitor.Enter
锁定 obj 这个对象,并且设置 acquiredLock 为 true,告诉别人 obj 已经被锁定。
最后结束时,判断 acquiredLock ,释放锁,并设置 acquiredLock 为 false。
解释一下
临界区:指被某些符号包围的范围。例如 {}
内。
Monitor 对象的 Enter 和 Exit 方法来标记临界区的开头和结尾。
Enter()
方法获取锁后,能够保证只有单个线程能够使用临界区中的代码。使用 Monitor 类,最好搭配 try{...}catch{...}finally{...}
来使用,因为如果获取到锁但是没有释放锁的话,会导致其它线程无限阻塞,即发生死锁。
一般来说,lock 关键字够用了。
示例
下面示范了多个线程如何使用 Monitor 来实现锁:
private static object obj = new object();
private static bool acquiredLock = false;
static void Main(string[] args)
{
new Thread(Test1).Start();
Thread.Sleep(1000);
new Thread(Test2).Start();
}
public static void Test1()
{
try
{
Monitor.Enter(obj, ref acquiredLock);
for (int i = 0; i < 10; i++)
{
Console.WriteLine("Test1正在锁定资源");
Thread.Sleep(1000);
}
}
catch { }
finally
{
if (acquiredLock)
Monitor.Exit(obj);
Console.WriteLine("Test1已经释放资源");
}
}
public static void Test2()
{
bool isGetLock = false;
Monitor.Enter(obj);
try
{
Monitor.Enter(obj, ref acquiredLock);
for (int i = 0; i < 10; i++)
{
Console.WriteLine("Test2正在锁定资源");
Thread.Sleep(1000);
}
}
catch { }
finally
{
if (acquiredLock)
Monitor.Exit(obj);
Console.WriteLine("Test2已经释放资源");
}
}
设置获取锁的时效
如果对象已经被锁定,另一个线程使用 Monitor.Enter
对象,就会一直等待另一个线程解除锁定。
但是,如果一个线程发生问题或者出现死锁的情况,锁一直被锁定呢?或者线程具有时效性,超过一段时间不执行,已经没有了意义呢?
我们可以通过 Monitor.TryEnter()
来设置等待时间,超过一段时间后,如果锁还没有释放,就会返回 false。
改造上面的示例如下:
private static object obj = new object();
private static bool acquiredLock = false;
static void Main(string[] args)
{
new Thread(Test1).Start();
Thread.Sleep(1000);
new Thread(Test2).Start();
}
public static void Test1()
{
try
{
Monitor.Enter(obj, ref acquiredLock);
for (int i = 0; i < 10; i++)
{
Console.WriteLine("Test1正在锁定资源");
Thread.Sleep(1000);
}
}
catch { }
finally
{
if (acquiredLock)
Monitor.Exit(obj);
Console.WriteLine("Test1已经释放资源");
}
}
public static void Test2()
{
bool isGetLock = false;
isGetLock = Monitor.TryEnter(obj, 500);
if (isGetLock == false)
{
Console.WriteLine("锁还没有释放,我不干活了");
return;
}
try
{
Monitor.Enter(obj, ref acquiredLock);
for (int i = 0; i < 10; i++)
{
Console.WriteLine("Test2正在锁定资源");
Thread.Sleep(1000);
}
}
catch { }
finally
{
if (acquiredLock)
Monitor.Exit(obj);
Console.WriteLine("Test2已经释放资源");
}
}
对于锁的使用,还有很多高级复杂的技术,本文简单地介绍了 Lock 和 Monitor 的使用。
随着教程的深入,会继续学习很多高级的使用方法。
C#多线程系列(2):多线程锁lock和Monitor的更多相关文章
- python 多线程中的同步锁 Lock Rlock Semaphore Event Conditio
摘要:在使用多线程的应用下,如何保证线程安全,以及线程之间的同步,或者访问共享变量等问题是十分棘手的问题,也是使用多线程下面临的问题,如果处理不好,会带来较严重的后果,使用python多线程中提供Lo ...
- java多线程系列(一)---多线程技能
java多线程技能 前言:本系列将从零开始讲解java多线程相关的技术,内容参考于<java多线程核心技术>与<java并发编程实战>等相关资料,希望站在巨人的肩膀上,再通过我 ...
- 多线程系列(1)多线程基础和Thread
因为现项目中有用到多线程和并发的知识,所以打算近期补习一下多线程相关的内容.第一篇文章从最基础的开始,就是如何开启一个线程,如何启动线程和阻塞线程等,这篇文章分以下几点进行总结. 多线程初印象 多线程 ...
- 多线程系列之七:Read-Write Lock模式
一,Read-Write Lock模式 在Read-Write Lock模式中,读取操作和写入操作是分开考虑的.在执行读取操作之前,线程必须获取用于读取的锁.在执行写入操作之前,线程必须获取用于写入的 ...
- Java多线程系列——从菜鸟到入门
持续更新系列. 参考自Java多线程系列目录(共43篇).<Java并发编程实战>.<实战Java高并发程序设计>.<Java并发编程的艺术>. 基础 Java多线 ...
- java多线程系列 目录
Java多线程系列1 线程创建以及状态切换 Java多线程系列2 线程常见方法介绍 Java多线程系列3 synchronized 关键词 Java多线程系列4 线程交互(wait和 ...
- Java多线程系列--“JUC锁”03之 公平锁(一)
概要 本章对“公平锁”的获取锁机制进行介绍(本文的公平锁指的是互斥锁的公平锁),内容包括:基本概念ReentrantLock数据结构参考代码获取公平锁(基于JDK1.7.0_40)一. tryAcqu ...
- Java多线程系列--“JUC锁”04之 公平锁(二)
概要 前面一章,我们学习了“公平锁”获取锁的详细流程:这里,我们再来看看“公平锁”释放锁的过程.内容包括:参考代码释放公平锁(基于JDK1.7.0_40) “公平锁”的获取过程请参考“Java多线程系 ...
- Java多线程系列--“JUC锁”10之 CyclicBarrier原理和示例
概要 本章介绍JUC包中的CyclicBarrier锁.内容包括:CyclicBarrier简介CyclicBarrier数据结构CyclicBarrier源码分析(基于JDK1.7.0_40)Cyc ...
随机推荐
- Deepin中安装使用好用的字典GoldenDict
2020-03-21 23:08:17 不说废话直接来安装步骤: 打开Deepin的应用商店,输入GoldenDict查找: 找到后点击安装,然后等待一小会,电脑提示音告诉你已经安装完成: 然后再 ...
- 大牛都在用的IDEA调试技巧
导读 前天面试了一个985高校的实习生,问了他平时用什么开发工具,他想也没想的说IDEA,于是我抛砖引玉的问了一下IDEA的调试用过吧,你说说怎么设置断点条件?那孩子懵了,想了好一会对我说没用过,甚至 ...
- django复习 以及源码
django请求生命周期 在浏览器上输入网址会发生什么事?(地址会朝我对应的ip地址发送get请求,get请求遵循http协议)先进入实现了wsgi协议的web服务器---->进入django- ...
- 第十五周java实验作业
实验十五 GUI编程练习与应用程序部署 实验时间 2018-12-6 1.实验目的与要求 (1) 掌握Java应用程序的打包操作: Java程序的打包,程序编译完成后,程序员将.class文件压缩打 ...
- Prism 源码解读3-Modules加载
介绍 在软件开发过程中,总想组件式的开发方式,各个组件之间最好互不影响,独立测试.Prism的Modules很好的满足了这一点. 这个架构图很好了讲解了Prism的Modules的概念 Prism支持 ...
- VMware使用总结
1.处理器设置释疑 比如一个8核16线程处理器 处理器数量最多设置为8,而每个处理器的内核数量*处理器个数必须小于等于16. 2.虚拟网络编辑器 NAT模式中可通过NAT设置将内部端口映射到主机端口. ...
- TensorFlow 模型优化工具包 — 训练后整型量化
模型优化工具包是一套先进的技术工具包,可协助新手和高级开发者优化待部署和执行的机器学习模型.自推出该工具包以来, 我们一直努力降低机器学习模型量化的复杂性 (https://www.tensorfl ...
- 仅用200个样本就能得到当前最佳结果:手写字符识别新模型TextCaps
由于深度学习近期取得的进展,手写字符识别任务对一些主流语言来说已然不是什么难题了.但是对于一些训练样本较少的非主流语言来说,这仍是一个挑战性问题.为此,本文提出新模型TextCaps,它每类仅用200 ...
- Go解算法07整数反转
描述 给出一个 32 位的有符号整数,你需要将这个整数中每位上的数字进行反转. 示例 1: 输入: 123 输出: 321 示例 2: 输入: -123 输出: -321 示例 3: 输入: 120 ...
- java fork/join简单实践
我们知道,java8中有并行流,而并行流在后台的实现是通过fork/join池来完成的,例如: List<Integer> a = buildList(); List<Integer ...