多线程同步与锁机制
Posted goodgirlmia
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了多线程同步与锁机制相关的知识,希望对你有一定的参考价值。
本篇研究多线程同步与锁。
一、多线程同步
多线程同步,这里的“同步”是指“协同合作”的意思,是多个线程协同完成执行某个操作。在软件系统中,多线程主要运用在以下场景:
一个业务逻辑要做很多次同样操作才能完成,而且总体效率很低、需要花费较长时间。比如卖电影票,一个柜台的售票窗口只需要一个售票员(其他售票员就在空闲着等待下一场次,浪费了人力物力),而且卖一张电影票需要花费几分钟时间,一个场次假设卖50张,则需要几个小时,这就不能满足业务需要。这时候,要考虑多开几个售票窗口(或者提供淘票票、美团、大众点评等app方式售出),无论什么渠道,都看作是“线程”,这些线程都在做着相同一件事——就是把这些电影票一张一张地卖出,每售出一张,剩余的数量就减少一张,并且电影票号码是按顺序售出。
简单的售票例子:
/**************************************************** * 多线程同步例子----多窗口同时售票 *****************************************************/ classProgram { staticvoid Main(string[] args) { Tickets tickets = newTickets(1,50);
Thread thread1 = newThread(newParameterizedThreadStart(ThreadMethod)); thread1.Name = "线程1"; thread1.Start(tickets);
Thread thread2 = newThread(newParameterizedThreadStart(ThreadMethod)); thread2.Name = "线程2"; thread2.Start(tickets); }
staticvoid ThreadMethod(Object t) { Tickets o = t asTickets; bool run = true; while (run) { if (o.Quantity > 0) { Console.WriteLine(Thread.CurrentThread.Name + " 售出票:" + DateTime.Now.ToString("yyyyMMddhhmmss") + (o.TicketNumber++).ToString().PadLeft(3, '0') + ",剩余数量:" + (--o.Quantity)); } else { Console.WriteLine(Thread.CurrentThread.Name + " --票售完了--"); run = false; } } } }
///<summary> ///电影票类 ///</summary> classTickets { publicint TicketNumber { get; set; } publicint Quantity { get; set; } public Tickets(int number, int quantity) { this.TicketNumber = number; this.Quantity = quantity; } } |
输出结果:
上面代码,两个线程(看做是两个窗口)同时售票,但是输出结果却是出现无序的、跳号的混乱结果。这是因为线程1、2并发进行,同时读取并更新号段、数量,由于cpu资源调度,比如某个线程读取时另一个线程却对该数据做了修改,导致读取出的可能不是预期中的,最终输出结果就出现了类似跳号乱序甚至重复的结果,与预期的数据不一致。
这里除了涉及线程同步,还涉及到另外一个概念:线程安全。
线程安全就是多线程访问时,采用了加锁机制,当一个线程访问该类的某个数据时,进行保护,其他线程不能进行访问直到该线程读取完,其他线程才可使用。不会出现数据不一致或者数据污染。线程不安全就是不提供数据访问保护,有可能出现多个线程先后更改数据造成所得到的数据是脏数据。 ---百度百科中的定义。
类似这种当多个线程同时对一个对象执行读写操作的场景,需要考虑线程安全。解决线程安全常用的机制有lock(Java对应的同步机制是Synchronized)。lock的目的是防止多线程执行的时候出现并发操作问题,在其锁定的区域内,在这个时刻只允许一个线程操作、并按顺序执行。
所以,要解决上述问题,在上述黄色括号内加粗的代码段加上锁即可,比如:
lock (o) { if (o.Quantity > 0) { Console.WriteLine(Thread.CurrentThread.Name + " 售出票:" + DateTime.Now.ToString("yyyyMMddhhmmss") + (o.TicketNumber++).ToString().PadLeft(3, '0') + ",剩余数量:" + (--o.Quantity)); } else { Console.WriteLine(Thread.CurrentThread.Name + " --票售完了--"); run = false; } }
|
输出结果:
除了上述场景,还有另外一个常见的比如后台程序在做比较复杂或者耗时的处理时,前台在等待,这种情况界面出现“假死”,用户处于未知的等待。比如在winform开发中的主界面线程与后台线程的处理、进度条的实现,这些不详述啦。
总之使用多线程就是为了充分利用cpu的资源,尽量减少cpu的空闲率,提高程序的整体执行效率。不过由于线程也会消耗资源,一定程度上降低性能,所以也要注意在不同场景下的使用。
二、多线程加锁
上面总结在什么场景下使用多线程,以及实现多线程同步的例子,其中用了锁达到同步目的。那么,什么情况下使用锁?
一般情况下,当多线程同时访问一个共享资源时,则需要考虑锁,比如全局静态变量,某些缓存中的值,线程内部的变量不需要锁定。
Lock常见用法:
1、Lock(this):锁住当前实例。如果有多个类实例的话,lock锁定的只是当前类实例,对其它类实例无影响。
2、Lock(object):定义一个静态的object对象obj,然后在需要加锁的区域块lock(obj)。
下面说明这两者的区别:
把上面例子代码改造一下,把售票这个操作放到单独的类中去实现,然后两个线程分别实例化这个类去调用售票方法。
/*************************************************** * 多线程多实例加锁 ****************************************************/ classProgram { staticvoid Main(string[] args) { Tickets tickets = new Tickets(1, 30);
Program p = new Program(); Thread thread1 = new Thread(new ParameterizedThreadStart(p.ThreadMethod)); thread1.Name = "线程1"; thread1.Start(tickets);
Thread thread2 = new Thread(new ParameterizedThreadStart(p.ThreadMethod)); thread2.Name = "线程2"; thread2.Start(tickets); }
///<summary> ///实例化销售电影票类,调用售票操作 ///</summary> ///<param name="t"></param> void ThreadMethod(Object t) { SellTicket ts = new SellTicket(); ts.SellTickets(t); } }
///<summary> ///电影票类 ///</summary> classTickets { publicint TicketNumber { get; set; } publicint Quantity { get; set; } public Tickets(int number, int quantity) { this.TicketNumber = number; this.Quantity = quantity; } }
///<summary> ///销售电影票类 ///</summary> classSellTicket { privatereadonly staticobject obj = newobject();
///<summary> ///售票操作 ///</summary> ///<param name="t"></param> publicvoid SellTickets(Object t) { Tickets o = t as Tickets; bool run = true; while (run) { lock (this) //lock(o) //lock(obj) { if (o.Quantity > 0) { Console.WriteLine(Thread.CurrentThread.Name + " 售出票:" + DateTime.Now.ToString("yyyyMMddhhmmss") + (o.TicketNumber++).ToString().PadLeft(3, '0') + ",剩余数量:" + (--o.Quantity)); } else { Console.WriteLine(Thread.CurrentThread.Name + " --票售完了--"); run = false; } } } } } |
(1)用Lock(this),输出结果:
再运行一次:
(2)用Lock(object)
注意:
① 如果obj是静态变量,即private readonly static object obj = new object();
输出结果:成功的。
②如果obj是非静态变量,即private object obj = new object();
输出结果:线程并发,失败。
总结:
lock(this) ——当用在不同线程调不同实例的情况下,lock锁定的只是当前类实例,对其它类实例无影响,所以会出现在同一时刻两个线程同时读写一个公共资源,以及lock内的代码不是按照顺序执行,违反了同步的原则。
lock(object)——当用在不同线程调不同实例的情况下,如果object是静态变量,则成功;若object是非静态变量,则失败。当用在单实例情况下,object是静态与非静态,都成功。即:
多线程单实例非静态锁,线程没有并发(锁成功);
多线程单实例静态锁,线程没有并发(锁成功);
多线程多实例非静态锁,线程并发(锁失败);
多线程多实例静态锁,线程没有并发(锁成功)。
再来强调下同步lock的目的是防止多线程执行的时候出现并发操作问题,在其锁定的区域内,在这个时刻只允许一个线程操作,也就是说当对同一个资源进行读写的时候,我们要使该资源在同一时刻只能被一个线程操作。
最后,Lock还可以用Monitor代替,lock就是用 Monitor 类来实现的,即lock是Monitor的封装。如上述例子加了lock的代码段可以改成如下:
Monitor.Enter(o); if (o.Quantity > 0) { Console.WriteLine(Thread.CurrentThread.Name + " 售出票:" + DateTime.Now.ToString("yyyyMMddhhmmss") + (o.TicketNumber++).ToString().PadLeft(3, '0') + ",剩余数量:" + --o.Quantity); } else { Console.WriteLine(Thread.CurrentThread.Name + " --票售完了--"); run = false; } Monitor.Exit(o); |
本篇就不详细讨论,下一篇再继续研究。
以上是关于多线程同步与锁机制的主要内容,如果未能解决你的问题,请参考以下文章