C# 温故而知新: 线程篇(三)下
结果计时器会一直滚动,因为a对象被锁住,除非完成Thread.Sleep(3000000)后才能进入到a共享区
由于以上的问题,微软还是建议我们使用一个私有的变量来锁定,由于私有变量外界无法访问,所以锁住话死锁的可能性大大下降了。
这样我们就能选择正确的“门”来进行锁住,但是可能还有一种可能也会造成死锁,就是在lock内部出现了问题,由于死锁非常复杂,我将在
今后的文章中专门写一篇关于死锁的文章来深入解释下死锁,所以这里就对死锁不深究了,这里大伙了解下lock的使用方法和注意事项就行了。
5.ReaderWriterLock
由于lock关键字对临界区(共享区)保护的非常周密,导致了一些功能可能会无法实现,假设我将某个查询功能放置在临界区中时,可能
当别的线程在查询临界区中的数据时,可能我的那个线程被阻塞了,所以我们期望锁能够达到以下功能
1 首先锁能细分为读锁和写锁
2 能够保证同时可以让多个线程读取数据
3 能保证同一时刻只有一个线程能进行写操作,也就是说,对于写操作,它必须拥有独占锁
4 能保证一个线程同一时刻只能拥有写锁或读锁中的一个
显然lock关键字无法满足我们的需求,还好微软想到了这点,ReaderWriterLock便隆重登场了ReaderWriterLock能够达到的效果是:
1. 同一时刻,它允许多个读线程同时访问临界区,或者允许单个线程进行写访问
2. 在读访问率很高,而且写访问率很低的情况下,效率最高,
3.它也满足了同一时刻只能获取写锁或读锁的要求。
4. 最为关键的是,ReaderWriterLock能够保证读线程锁和写线程锁在各自的读写队列中,当某个线程释放了写锁了,同时读线程队列中
的所有线程将被授予读锁,同样,当所有的读锁被释放时,写线程队列中的排队的下一个线程将被授予写锁,更直观的说,ReaderWriterLock
就是在这几种状态间来回切换
5 使用时注意每当你使用AcquireXXX方法获取锁时,必须使用ReleaseXXX方法来释放锁
6 ReaderWriterLock 支持递归锁,关于递归锁会在今后的章节详细阐述
7 在性能方面ReaderWriterLock做的不够理想,和lock比较差距明显,而且该类库中还隐藏些bug,有于这些原因,微软又专门重新写了个新
类ReaderWriterLockSilm来弥补这些缺陷。
8 处理死锁方面ReaderWriterLock为我们提供了超时的参数这样我们便可以有效的防止死锁
9 对于一个个获取了读锁的线程来说,在写锁空闲的情况下可以升级为写锁
接着让我们了解下ReaderWriterLock的重要成员
上述4个方法分别是让线程获取写锁和读锁的方法,它利用的计数的概念,当一个线程中调用此方法后,该类会给该线程拥有的锁计数加1
(每次加1,但是一个线程可以拥有多个读锁,所以计数值可能更多,但是对于写锁来说同时一个一个线程可以拥有)。后面的参数是超时
时间,我们可以自己设置来避免死锁。同样调用上述方法后我们必须使用ReleaseXXX 方法来让计数值减1,直到该线程拥有锁的计数为0,
释放了锁为止。
最后我们用一个简单的例子来温故下上述的知识点(请注意看注释)
/// <summary>
/// 该示例通过ReaderWriterLock同步来实现Student集合多线程下
/// 的写操作和读操作
/// </summary>
class Program
{
static ReaderWriterLock _readAndWriteLock = new ReaderWriterLock();
static List<Student> demoList = new List<Student>();
static void Main(string[] args)
{
InitialStudentList();
Thread thread=null;
for (int i = 0; i <5; i++)
{
//让第前2个个线程试图掌控写锁,
if (i < 2)
{
thread = new Thread(new ParameterizedThreadStart(AddStudent));
Console.WriteLine("线程ID:{0}, 尝试获取写锁 ", thread.ManagedThreadId);
thread.Start(new Student { Name = "Zhang" + i });
}
else
{
//让每个线程都能访问DisplayStudent 方法去获取读锁
thread = new Thread(new ThreadStart(DisplayStudent));
thread.Start();
}
Thread.Sleep(20);
}
Console.ReadKey();
}
static void InitialStudentList()
{
demoList = new List<Student> { new Student{ Name="Sun"}, new Student{Name="Zheng"} };
}
/// <summary>
/// 当多个线程试图使用该方法时,只有一个线程能够透过AcquireSWriterLock
/// 获取写锁,同时其他线程进入队列中等待,直到该线程使用ReleaseWriterLock后
/// 下个线程才能进入拥有写锁
/// </summary>
/// <param name="student"></param>
static void AddStudent(object student)
{
if (student == null|| !(student is Student)) return;
if (demoList.Contains(student)) return;
try
{
//获取写锁
_readAndWriteLock.AcquireWriterLock(Timeout.Infinite);
demoList.Add(student as Student);
Console.WriteLine("当前写操作线程为{0}, 写入的学生是:{1}", Thread.CurrentThread.ManagedThreadId,(student as Student).Name);
}
catch (Exception)
{
}
finally
{
_readAndWriteLock.ReleaseWriterLock();
}
}
/// <summary>
/// 对于读锁来所,允许多个线程共同拥有,所以这里同时
/// 可能会有多个线程访问Student集合,使用try catch是为了
/// 一定要让程序执行finally语句块中的releaseXXX方法,从而保证
/// 能够释放锁
/// </summary>
static void DisplayStudent()
{
try
{
_readAndWriteLock.AcquireReaderLock(Timeout.Infinite);
demoList.ForEach(student
=>
{
Console.WriteLine("当前集合中学生为:{0},当前读操作线程为{1}", student.Name, Thread.CurrentThread.ManagedThreadId);
});
}
catch (Exception)
{
}
finally
{
_readAndWriteLock.ReleaseReaderLock();
}
}
}
internal class Student
{
public string Name { get; set; }
}
运行结果:
从例子可以看出有2个线程试图尝试争取写锁,但是同时只有一个线程可以获取到写锁,同时对于读取集合的线程可以同时获取多个读锁
6. 本章总结
由于本人上个月工作突然忙了起来,快一个多月没更新博客了,希望大家可以见谅^^
本章介绍了线程同步的概念和一些关于同步非常重要的基本概念,对于原子性的操作的认识也格外重要,同时对于Volatile,Interlocked,lock,ReaderWriterLock 知识点做了相关介绍,
相信大家对于线程同步有个初步的认识和理解,在写本篇博客时,发现死锁也是个很重要的知识点,关于死锁我会单独写篇文章来阐述,谢谢大家的支持!
7. 参考资料
CLR via c#
msdn
- Linux SSH密码暴力破解技术及攻防实战
- 西部数据NAS设备被曝存在硬编码后门和未授权文件上传高危漏洞
- Hive & Performance 学习笔记
- 任意用户密码重置(一):重置凭证泄漏
- linux 系统监控、诊断工具之 top 详解
- 一个二进制POC的诞生之旅CVE-2018-0802
- 远程RPC溢出EXP编写实战之MS06-040
- 浮点数加法引发的问题:浮点数的二进制表示
- 新手科普 | MySQL手工注入之基本注入流程
- linux 系统监控、诊断工具之 lsof 用法简介
- 关于 SimpleDateFormat 的非线程安全问题及其解决方案
- 关于 WEB/HTTP 调试利器 Fiddler 的一些技巧分享
- Java线程使用技巧学习(一)
- Python FAQ(常见问题解答)(1)
- JavaScript 教程
- JavaScript 编辑工具
- JavaScript 与HTML
- JavaScript 与Java
- JavaScript 数据结构
- JavaScript 基本数据类型
- JavaScript 特殊数据类型
- JavaScript 运算符
- JavaScript typeof 运算符
- JavaScript 表达式
- JavaScript 类型转换
- JavaScript 基本语法
- JavaScript 注释
- Javascript 基本处理流程
- Javascript 选择结构
- Javascript if 语句
- Javascript if 语句的嵌套
- Javascript switch 语句
- Javascript 循环结构
- Javascript 循环结构实例
- Javascript 跳转语句
- Javascript 控制语句总结
- Javascript 函数介绍
- Javascript 函数的定义
- Javascript 函数调用
- Javascript 几种特殊的函数
- JavaScript 内置函数简介
- Javascript eval() 函数
- Javascript isFinite() 函数
- Javascript isNaN() 函数
- parseInt() 与 parseFloat()
- escape() 与 unescape()
- Javascript 字符串介绍
- Javascript length属性
- javascript 字符串函数
- Javascript 日期对象简介
- Javascript 日期对象用途
- Date 对象属性和方法
- Javascript 数组是什么
- Javascript 创建数组
- Javascript 数组赋值与取值
- Javascript 数组属性和方法
- pandas to_excel 添加颜色操作
- Python自带的IDE在哪里
- php+Ajax无刷新验证用户名操作实例详解
- PHP经典设计模式之依赖注入定义与用法详解
- 浅谈python出错时traceback的解读
- PHP判断是否是微信打开还是浏览器打开的方法
- php面向对象程序设计中self与static的区别分析
- PHP如何根据文件头检测文件类型实例代码
- PHP去除空数组且数组键名重置的讲解
- Yii2框架配置文件(Application属性)与调试技巧实例分析
- java解析json方法总结
- PHP正则验证字符串是否为数字的两种方法并附常用正则
- 使用python编写一个语音朗读闹钟功能的示例代码
- PHP中常见的密码处理方式和建议总结
- php+Ajax处理xml与json格式数据的方法示例