一、线程同步中的一些概念

  1.1临界区(共享区)的概念

  在多线程的环境中,可能需要共同使用一些公共资源,这些资源可能是变量,方法逻辑段等等,这些被多个线程共用的区域统称为临界区(共享区),临界区的资源不是很安全,因为线程的状态是不定的,所以可能带来的结果是临界区的资源遭到其他线程的破坏,我们必须采取策略或者措施让共享区数据在多线程的环境下保持完成性不让其受到多线程访问的破坏。

  1.2基元用户模式

  基元用户模式是指使用cpu的特殊指令来调度线程,所以这种协调调度线程是在硬件中进行的所以得出了它第一些优点:

  • 速度特别快;
  • 线程阻塞时间特别短;

  但是由于该模式中的线程可能被系统抢占,导致该模式中的线程为了获取某个资源,而浪费许多cpu时间,同时如果一直处于等待的话会导致”活锁”,也就是既浪费了内存,又浪费了cpu时间,这比下文中的死锁更可怕,那么如何利用强大的cpu时间做更多的事呢?那就引出了下面的一个模式

  1.3基元内核模式

  该模式和用户模式不同,它是windows系统自身提供的,使用了操作系统中内核函数,所以它能够阻塞线程提高了cpu的利用率,同时也带来了一个很可怕的bug,死锁,可能线程会一直阻塞导致程序的奔溃,常用的内核模式的技术例如Monitor,Mutex,等等会在下一章节介绍。本章将详细讨论锁的概念,使用方法和注意事项

  1.4原子性操作

  如果一个语句执行一个单独不可分割的指令,那么它是原子的。严格的原子操作排除了任何抢占的可能性,更方便的理解是这个值永远是最新的,在c#中原子操作如下图所示:其实要符合原子操作必须满足以下条件c#中如果是32位cpu的话,为一个少于等于32位字段赋值是原子操作,其他(自增,读,写操作)的则不是。对于64位cpu而言,操作32或64位的字段赋值都属于原子操作其他读写操作都不能属于原子操作相信大家能够理解原子的特点,所以在使用原子操作时也需要注意当前操作系统是32位或是64位cpu或者两者皆要考虑。

  1.5非阻止同步

  非阻止同步:不阻止其他线程的情况下实现同步。就是利用原子性操作实现线程间的同步,不刻意阻塞线程,减少相应线程的开销,interlocked类便是c#中非阻止同步的理念所产生的线程同步技术。

  1.6阻止同步

  阻止同步:阻止其他线程,同一时间只允许单个线程访问临界资源。其实阻止同步也是基元内核模式的特点之一。

  例如c# 中的锁机制,及mutex,monitor等都属于阻止同步,他们的根本目的是,以互斥的效果让同一时间只有一个线程能够访问共享区,其他线程必须阻止等待,直到该线程离开共享区后,才让其他一个线程访问共享区,阻止同步缺点也是容易产生死锁,但是阻止同步提高了cpu时间的利用率。

二、为何需要同步

  当多个线程同时访问某个资源,可能造成意想不到的结果。如多个线程同时访问静态资源。

    class Program{static void Main(string[] args){//初始化10个线程1去访问numfor (int i = 0; i < 10; i++){ThreadPool.QueueUserWorkItem(new WaitCallback(Run));}Console.ReadKey();}static int num = 0;static void Run(object state){Console.WriteLine("当前数字:{0}", ++num);}}

  输出如下:

  

  我们看到,num++按照逻辑,应该是1,2,3,4,5,6,7,8,9,10。这就是多个线程去访问,顺序乱套了。这时候就需要同步了。

三、原子操作同步原理

  Thread类中的VolatileRead和VolatileWrite方法:

  • VolatileWrite:当线程在共享区(临界区)传递信息时,通过此方法来原子性的写入最后一个值;
  • VolatileRead:当线程在共享区(临界区)传递信息时,通过此方法来原子性的读取第一个值;
    class Program{static Int32 count;//计数值,用于线程同步 (注意原子性,所以本例中使用int32)static Int32 value;//实际运算值,用于显示计算结果static void Main(string[] args){//读线程Thread thread2 = new Thread(new ThreadStart(Read));thread2.Start();//写线程for (int i = 0; i < 10; i++){Thread.Sleep(20);Thread thread = new Thread(new ThreadStart(Write));thread.Start();}Console.ReadKey();}/// <summary>/// 实际运算写操作/// </summary>private static void Write(){Int32 temp = 0;for (int i = 0; i < 10; i++){temp += 1;}//真正写入value += temp;Thread.VolatileWrite(ref count, 1);}/// <summary>///  死循环监控读信息/// </summary>private static void Read(){while (true){//死循环监听写操作线执行完毕后立刻显示操作结果if (Thread.VolatileRead(ref count) > 0){Console.WriteLine("累计计数:{1}", Thread.CurrentThread.ManagedThreadId, value);count = 0;}}}}

  输出如下:

  

三、Volatile关键字

  Volatile关键字的本质含义是告诉编译器,声明为Volatile关键字的变量或字段都是提供给多个线程使用的。Volatile无法声明为局部变量。作为原子性的操作,Volatile关键字具有原子特性,所以线程间无法对其占有,它的值永远是最新的。

  Volatile支持的类型:

  • 引用类型;
  • 指针类型(在不安全的上下文中);
  • 类型,如 sbyte、byte、short、ushort、int、uint、char、float 和 bool;
  • 具有以下基类型之一的枚举类型:byte、sbyte、short、ushort、int 或 uint;
  • 已知为引用类型的泛型类型参数;
  • IntPtr 和 UIntPtr;
class Program{static volatile Int32 count;//计数值,用于线程同步 (注意原子性,所以本例中使用int32)static Int32 value;//实际运算值,用于显示计算结果static void Main(string[] args){//开辟一个线程专门负责读value的值,这样就能看见一个计算的过程Thread thread2 = new Thread(new ThreadStart(Read));thread2.Start();//开辟10个线程来负责计算,每个线程负责1000万条数据for (int i = 0; i < 10; i++){Thread.Sleep(20);Thread thread = new Thread(new ThreadStart(Write));thread.Start();}Console.ReadKey();}/// <summary>/// 实际运算写操作/// </summary>private static void Write(){Int32 temp = 0;for (int i = 0; i < 10; i++){temp += 1;}value += temp;//告诉监听程序,我改变了,读取最新吧!count = 1;}/// <summary>///  死循环监听/// </summary>private static void Read(){while (true){if (count == 1){Console.WriteLine("累计计数:{1}", Thread.CurrentThread.ManagedThreadId, value);count = 0;}}}}

  输出:

  

四、lock关键字

  lock的作用在于同一时间确保一个对象只允许一个线程访问。

  lock的语法如下:

   static object obj = new object();lock (obj){//语句块}

  我们使用lock来改写上面的示例:

    class Program{static void Main(string[] args){//初始化10个线程1去访问numfor (int i = 0; i < 10; i++){ThreadPool.QueueUserWorkItem(new WaitCallback(Run));}Console.ReadKey();}static int num = 0;static object obj = newobject();static void Run(object state){lock (obj){Console.WriteLine("当前数字:{0}", ++num);}}}

  输出如下:

  

五、Monitor.Enter与Monitor.Exit

  Monitor.Enter和Monitor.Exit这个东西跟lock的作用一样。事实上。lock就是Monitor.Enter和Monitor.Exit的包装。

  下面用Monitor.Enter与Monitor.Exit来实现相同的代码:

    class Program{static void Main(string[] args){//初始化10个线程1去访问numfor (int i = 0; i < 10; i++){ThreadPool.QueueUserWorkItem(new WaitCallback(Run));}Console.ReadKey();}static int num = 0;static object obj = new object();static void Run(object state){//获取排他锁Monitor.Enter(obj);Console.WriteLine("当前数字:{0}", ++num);//释放排它锁Monitor.Exit(obj);}}

六、Monitor.Wait与Monitor.Pulse

  Wait() 和 Pulse() 机制用于线程间交互:

  • Wait() 释放锁定资源,进入等待状态直到被唤醒;
  • Pulse() 和 PulseAll() 方法用来通知Wait()的线程醒来;
    class Program{static void Main(string[] args){Thread t1 = new Thread(Run1);Thread t2 = new Thread(Run2);t1.Start();t1.Name = "刘备";t2.Start();t2.Name = "关羽";Console.ReadKey();}static object obj = new object();static void Run1(object state){Monitor.Enter(obj);Console.WriteLine(Thread.CurrentThread.Name + ":二弟,你上哪去了?");Monitor.Wait(obj);      //暂时释放锁,让关羽线程进入Console.WriteLine(Thread.CurrentThread.Name + ":你混蛋!");Monitor.Pulse(obj);     //唤醒关羽线程 Monitor.Exit(obj);}static void Run2(object state){Monitor.Enter(obj);Console.WriteLine(Thread.CurrentThread.Name + ":老子跟曹操了!");Monitor.Pulse(obj);     //唤醒刘备线程Monitor.Wait(obj);     //暂停本线程Console.WriteLine(Thread.CurrentThread.Name + ":投降吧,曹孟德当世英雄,竖子不足与谋!!");Monitor.Exit(obj);}}

  输出如下:

  

七、读写锁ReadWriterLock

  写入串行,读取并行;

  如果程序中大部分都是读取数据的,那么由于读并不影响数据,ReadWriterLock类能够实现”写入串行“,”读取并行“。

  常用方法如下:

  • AcquireWriterLock: 获取写入锁; ReleaseWriterLock:释放写入锁。
  • AcquireReaderLock: 获取读锁; ReleaseReaderLock:释放读锁。
  • UpgradeToWriterLock:将读锁转为写锁;DowngradeFromWriterLock:将写锁还原为读锁。
   class Program{static List<string> ListStr = new List<string>();static ReaderWriterLock rw = new System.Threading.ReaderWriterLock();static void Main(string[] args){Thread t1 = new Thread(Run1);Thread t2 = new Thread(Run2);t1.Start();t1.Name = "刘备";t2.Start();t2.Name = "关羽";Console.ReadKey();}static object obj = new object();static void Run1(object state){//获取写锁2秒rw.AcquireWriterLock(2000);Console.WriteLine(Thread.CurrentThread.Name + "正在写入!");ListStr.Add("曹操混蛋");ListStr.Add("孙权王八蛋");Thread.Sleep(1200);ListStr.Add("周瑜个臭小子");rw.ReleaseWriterLock();}//此方法异常,超时,因为写入时不允许读(那么不用测也能猜到更加不允许写咯)static void Run2(object state){//获取读锁1秒rw.AcquireReaderLock(1000);Console.WriteLine(Thread.CurrentThread.Name + "正在读取!");foreach (string str in ListStr){Console.WriteLine(str);}rw.ReleaseReaderLock();}}

  异常如下:

  

  下面是读取并行的例子:

    class Program{static List<string> ListStr = new List<string>();static ReaderWriterLock rw = new System.Threading.ReaderWriterLock();static void Main(string[] args){ListStr.Add("貂蝉");ListStr.Add("西施");ListStr.Add("王昭君");Thread t1 = new Thread(Run1);Thread t2 = new Thread(Run2);t1.Start();t1.Name = "刘备";t2.Start();t2.Name = "关羽";Console.ReadKey();}static object obj = new object();static void Run1(object state){//获取写锁2秒rw.AcquireReaderLock(2000);Console.WriteLine(Thread.CurrentThread.Name + "正在读取!");foreach (string str in ListStr){Console.WriteLine(Thread.CurrentThread.Name + "在读:" + str);}rw.ReleaseReaderLock();}//此方法异常,超时,因为写入时不允许读(那么不用测也能猜到更加不允许写咯)static void Run2(object state){//获取读锁1秒rw.AcquireReaderLock(1000);Console.WriteLine(Thread.CurrentThread.Name + "正在读取!");foreach (string str in ListStr){Console.WriteLine(Thread.CurrentThread.Name + "在读:" + str);}rw.ReleaseReaderLock();}}

  输出如下:

  

  总结:写入锁与任何锁都不兼容,读取锁与读取锁可以兼容。

转载于:https://www.cnblogs.com/Jeely/p/10750685.html

转载 锁机制与原子操作 第四篇相关推荐

  1. 锁机制与原子操作 第四篇

    锁机制与原子操作 <第四篇> 一.线程同步中的一些概念 1.1临界区(共享区)的概念 在多线程的环境中,可能需要共同使用一些公共资源,这些资源可能是变量,方法逻辑段等等,这些被多个线程共用 ...

  2. 大话Linux内核中锁机制之原子操作、自旋锁【转】

    转自:http://blog.sina.com.cn/s/blog_6d7fa49b01014q7p.html 多人会问这样的问题,Linux内核中提供了各式各样的同步锁机制到底有何作用?追根到底其实 ...

  3. 对象头、锁的四种状态、Java和处理器实现原子操作的方式(CAS、锁机制;总线锁定、缓存锁定)

    1.对象头 Java对象头里的Mark Word里默认存储对象的HashCode.分代年龄和锁标记位. 32位JVM的Mark Word的默认存储结构如下图所示: 在运行期间,Mark Word里存储 ...

  4. [转载] 数据库分析手记 —— InnoDB锁机制分析

    作者:倪煜 InnoDB锁机制常常困扰大家,不同的条件下往往表现出不同的锁竞争,在实际工作中经常要分析各种锁超时.死锁的问题.本文通过不同条件下的实验,利用InnoDB系统给出的各种信息,分析了锁的工 ...

  5. java锁的概念,Java ReentrantLock锁机制概念篇

    分享Java锁机制实现原理,细节涉及volatile修饰符.CAS原子操作.park阻塞线程与unpark唤醒.双向链表.锁的公平性与非公平性.独占锁和共享锁.线程等待await.线程中断interr ...

  6. JUC并发编程第十四篇,StampedLock(邮戳锁)为什么比ReentrantReadWriteLock(读写锁)更快!

    JUC并发编程第十四篇,StampedLock(邮戳锁)为什么比ReentrantReadWriteLock(读写锁)更快! 一.ReentrantReadWriteLock(读写锁) 1.读写锁存在 ...

  7. MySQL基础篇(06):事务管理,锁机制案例详解

    本文源码:GitHub·点这里 || GitEE·点这里 一.锁概念简介 1.基础描述 锁机制核心功能是用来协调多个会话中多线程并发访问相同资源时,资源的占用问题.锁机制是一个非常大的模块,贯彻MyS ...

  8. 第四篇:服务发现机制

    本文出自Service Discovery in a Microservices Architecture,作者 Chris Richardson, 写于2015年5月19日 这是本系列文章的第四篇. ...

  9. 详解 Java 常用的四种锁机制优缺点

    多线程的并发问题一直困扰着大家,Java提供了多种多线程锁机制的实现方式,接下来的话题将分为四个部分给大家讲解他们的优缺点和原理(Synchronized.ReentrantLock.Semaphor ...

最新文章

  1. hashmap 扩容是元素还是数组_HashMap的扩容机制---resize()
  2. 贝叶斯网络之父Judea Pearl:要建立真正的人工智能,少不了因果推理
  3. 如何编程实现开启或关闭GPS(转)
  4. CNN应用之基于R-CNN的物体检测-CVPR 2014-未完待续
  5. 为什么要在2021年放弃Jenkins?我已经对他失去耐心了...
  6. php 中文字符串长度_php中计算中文字符串长度、截取中文字符串的函数代码
  7. 翻译:AI数据科学认证-2021年的最佳选择
  8. 一个计算机软件学生的求职简历,计算机学生求职的个人简历模板
  9. java如何调用百度地图拾取坐标系统
  10. kubernetes pod 挂载 ceph rbd
  11. 了解区块链(一)——加密货币以及区块链的价值
  12. jquery实现按钮倒数7秒后才可以点击
  13. ASRT语音识别项目
  14. python做路径图_初学者福利:python路线图
  15. [转]基于mysql数据库binlog的增量订阅消费中间件:Canal
  16. 【Java基础系列教程】第一章 编程入门
  17. Javascript的调试利器:Firebug使用详解(上)
  18. matlab R2011a汉化包,MATLAB R2011a (7.12)发布了,MATLAB R2011a最新功能
  19. 新版本ubuntu13.10软件安装
  20. OSChina 周六乱弹 —— 篮球都不想跟你回家了

热门文章

  1. 苹果电子邮件怎么注册_忘记了Apple ID账号密码怎么办?超全攻略,帮你解决问题...
  2. 31 家企业入选阿里云首期云原生加速器,共建云原生行业新生态
  3. keyshot怎么贴logo_KeyShot图文教程,三步教你如何使用添加有织纹的Logo
  4. Centos7.6下构建虚拟主机实验(基于域名、端口及IP地址)
  5. EXCEL中空白单元格如何快速填充为0
  6. 全网最细MySQL数据库下载及安装教程
  7. 解决Jenkins中Maven本地仓库更新不及时的问题
  8. 【Python实用工具】速来!!一篇文章十分钟教你如何使用Python第三方库basemap进行地图绘制
  9. 大物知识点复习框架——光学
  10. 利用Photoshop通道制作BMP格式的透明图标