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
- 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 数组属性和方法
- 不要再对类别变量进行独热编码了
- 面试Java基础问题汇总
- K8s集群上使用Helm部署2.4.6版本Rancher集群
- 一个工作三年的同事,居然还搞不清深拷贝、浅拷贝...
- 太有意思了,教你实现实现王者荣耀团战!
- 动画:什么是基数排序?
- 一个有意思的分钱模拟问题
- 如何快速的开发一个完整的直播购物源码,基础篇
- 「拥抱开源」Nacos 实战篇
- 仅2M!免费软件又一次干掉了付费版
- python爬虫学习 爬取幽默笑话网站
- 如何用Python快速优雅的批量修改Word文档样式?
- 为什么MySQL不推荐使用uuid或者雪花id作为主键?
- 用Python打造一款文件搜索工具,所有功能自己定义!
- 用Python绘制诱人的桑基图,一眼看透熬夜和狗粮的秘密