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

原文发布于微信公众号 - 我为Net狂(dotNetCrazy)

原文发表时间:2016-07-01

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

发表于

我来说两句

0 条评论
登录 后参与评论

相关文章

来自专栏java一日一条

2015年Java开发岗位面试题归类

3. 说说你知道的几个Java集合类:list、set、queue、map实现类咯。。。

991
来自专栏青玉伏案

iOS逆向工程之Hopper中的ARM指令

虽然前段时间ARM被日本软银收购了,但是科技是无国界的,所以呢ARM相关知识该学的学。现在看ARM指令集还是倍感亲切的,毕竟大学里开了ARM这门课,并且做了不少...

3357
来自专栏栗霖积跬步之旅

java多线程编程核心技术——第四章总结

第一节使用ReentrantLock类 1.1使用ReentrantLock实现同步:测试1   使用下面代码获取ReenTrantLock对象lock pri...

2426
来自专栏Kirito的技术分享

JAVA 拾遗--Future 模式与 Promise 模式

写这篇文章的动机,是缘起于微信闲聊群的一场讨论,粗略整理下,主要涉及了以下几个具体的问题: 同步,异步,阻塞,非阻塞的关联及区别。 JAVA 中有 callb...

2.9K10
来自专栏菩提树下的杨过

thrift 一个有意思的特性:Class名称无关性

最近开发的一个项目,后端采用thrift框架来提供rpc服务(java语言实现),然后前端采用php语言来生成thrift client调用后台RPC服务。由于...

2058
来自专栏小樱的经验随笔

堆和栈的区别

一、预备知识—程序的内存分配 一个由c/C++编译的程序占用的内存分为以下几个部分 1、栈区(stack)— 由编译器自动分配释放 ,存放函数的参数值,局部变量...

3629
来自专栏喵了个咪的博客空间

zephir-(2)安装和初体验

zephir-安装和初体验 ? 前言 先在这里感谢各位zephir开源技术提供者 zephir主要是解决了PHP开发人员尝试编写和编译PHP拓展所能执行的代码的...

4056
来自专栏JavaEdge

用弱引用堵住内存泄漏全局 Map 造成的内存泄漏找出内存泄漏HPROF 输出,显示 Map.Entry 对象的分配点弱引用WeakReference.get() 的一种可能实现用 WeakHashMa

3605
来自专栏编程

身为程序猿,怎能不懂RegExp?

正则表达式是程序猿的好朋友。这体现在两个方面:一、在我们敲的代码里面,可以用正则表达式非常轻巧、灵便、快捷的完成字符串的操作,比如匹配、搜索、提取子串等。二、我...

2145
来自专栏我是攻城师

浅谈Lucene中的DocValues

3823

扫码关注云+社区

领取腾讯云代金券