《线程》——多线程同步实例剖析
2016-02-15 08:16
246 查看
线程这个名词我们在学习操作系统的时候就接触过了,线程又称为轻量级进程,那进程是什么哪?大家可以跟随我的超链接看一下百度百科的解释。
1、简单线程实例,解决两个售票窗口售票问题。
具体的业务逻辑是:有两个售票台共同售票,票的总数是一定的(count),售票台1和售票台2共同访问票的总数count,我们开启两个线程使两个售票台共同售票,那么会出现什么情况那?请看下面的代码
1.1、两个线程没有同步前的代码——读脏数据
Tick类
效果图如下所示
问题一:我将前七条记录编号,从上往下看这七条记录,1号位置余票是99张,2号位置余票是97张,更搞笑的是都是售票台1在卖票,没道理啊!那第98张票去哪了?
问题二:再看4号位与5号位,4号位余票95张,是售票台1在卖票,5号位余票98张,是售票台2在卖票,哎,第98张票终于找到了,可是问题又来了,票的总数是一定的,售票台1和售票台2都是在同一个售票点卖票,从4号位的95张余票再到5号位的98张余票,车票竟然越卖越多了,这是怎么回事?
问题三:再看5号位与6号位,都是售票台2在卖票,就卖了一次票(一次卖一张),余票从98变成了93,不应变成97才对嘛,余票怎么少了4张,这究竟是怎么回事?
1.2、问题剖析
仔细看看代码,按照卖票的思维逻辑走一趟,先解屡屡问题一,余票从99张变成了97张,这是因为当售票台1卖完第一张票,剩余99张票的时候,售票台2插进来卖票了,也就是说线程2开始卖票了,线程2的代码执行到了Tick.count--;这个地方,此时count变成98,还没等线程2执行Console.WriteLine(System.Threading.Thread.CurrentThread.Name + ",剩余{0}张!", count)这段代码的时候,线程1又开始卖票,也就是说线程2的第一次循环还没有执行完就被迫让出CPU(单核)让线程一执行,线程2只好将此时的现场信息保存到自己的工作缓存中去(此时余票是98),这是为了等下次线程2在CPU中运行的时候根据现场信息再执行代码,也就是说当线程2再次接管CPU的时候是直接运行第一次循环的还没有执行的那段代码,也就是这段代码Console.WriteLine(System.Threading.Thread.CurrentThread.Name
+ ",剩余{0}张!", count)。所以余票剩余98张会出现在5号位置。
用分析问题一的思维逻辑分析问题二和问题三,就不用我写出来了吧!这就是多线程访问共享变量出现的问题,那么怎样才能解决这个问题哪?那么,线程同步该上场了,什么是线程同步?这个问题可以追溯的进程同步,点击超链接看看进程同步。
2、 现在给线程加锁试试,看下面的代码
看看效果图
从图中可以看出,当线程t1和线程t2切换的时候,也就是1号售票口与2号售票切换的时候,余票的数据是正确的。简单的加了一个锁就可以变成这样了。那加锁的原理是什么哪?先看一张图。
2.1、每个对象其实在内存中都会分配一个对象头空间,其中最后2个bit 用来存放锁标识,当线程1给某一代码块加锁后,对象头标识里面数据就变成了1,在线程1没有执行完具体的操作之前,也就是线程1没有跳出上面的那个锁之前(跳出锁后标识自动变为0),线程2是不能访问被锁住的代码块的,因为线程2在访问这个代码块之前,也会访问这个锁对象,判断锁标识,此时锁标识是1,不是0,所以线程2不能进入被锁住的代码块。对应上面的问题一和问题2,也就是说当线程1每次循环执行过程中,只要每次循环没有执行完毕,锁就不会释放,其它线程就不会访问共享变量count,也就不会造成数据不同步问题了。
2.2、lock的本质:调用Monitor对象的Enter()方法来加锁,调用Monitor的Exit()方法解锁。
3、小结
将上面的问题总结一下,线程有同步也需要前提:同步需要两个或者两个以上的线程、多个线程使用的是同一个锁。线程同步的特点:即使获取了CPU的时间片,也无法执行。线程同步缺点:当线程相当多时,因为每个线程都会去判断同步上的锁,这很耗费资源,降低程序的运行效率。所以说,线程也不是越多越好,要适当着用。
1、简单线程实例,解决两个售票窗口售票问题。
具体的业务逻辑是:有两个售票台共同售票,票的总数是一定的(count),售票台1和售票台2共同访问票的总数count,我们开启两个线程使两个售票台共同售票,那么会出现什么情况那?请看下面的代码
1.1、两个线程没有同步前的代码——读脏数据
class Program { static void Main(string[] args) { Tick t1 = new Tick("1号售票台 "); Tick t2 = new Tick("2号售票台 "); Console.ReadLine(); } }
Tick类
public class Tick { public static int count = 100; public Tick(string name) { Thread t = new Thread(sell); t.Name = name; t.IsBackground = true; t.Start(); } public void sell() { while (count > 1) { Tick.count--; Console.WriteLine(System.Threading.Thread.CurrentThread.Name + ",剩余{0}张!", count); } } }
效果图如下所示
问题一:我将前七条记录编号,从上往下看这七条记录,1号位置余票是99张,2号位置余票是97张,更搞笑的是都是售票台1在卖票,没道理啊!那第98张票去哪了?
问题二:再看4号位与5号位,4号位余票95张,是售票台1在卖票,5号位余票98张,是售票台2在卖票,哎,第98张票终于找到了,可是问题又来了,票的总数是一定的,售票台1和售票台2都是在同一个售票点卖票,从4号位的95张余票再到5号位的98张余票,车票竟然越卖越多了,这是怎么回事?
问题三:再看5号位与6号位,都是售票台2在卖票,就卖了一次票(一次卖一张),余票从98变成了93,不应变成97才对嘛,余票怎么少了4张,这究竟是怎么回事?
1.2、问题剖析
仔细看看代码,按照卖票的思维逻辑走一趟,先解屡屡问题一,余票从99张变成了97张,这是因为当售票台1卖完第一张票,剩余99张票的时候,售票台2插进来卖票了,也就是说线程2开始卖票了,线程2的代码执行到了Tick.count--;这个地方,此时count变成98,还没等线程2执行Console.WriteLine(System.Threading.Thread.CurrentThread.Name + ",剩余{0}张!", count)这段代码的时候,线程1又开始卖票,也就是说线程2的第一次循环还没有执行完就被迫让出CPU(单核)让线程一执行,线程2只好将此时的现场信息保存到自己的工作缓存中去(此时余票是98),这是为了等下次线程2在CPU中运行的时候根据现场信息再执行代码,也就是说当线程2再次接管CPU的时候是直接运行第一次循环的还没有执行的那段代码,也就是这段代码Console.WriteLine(System.Threading.Thread.CurrentThread.Name
+ ",剩余{0}张!", count)。所以余票剩余98张会出现在5号位置。
用分析问题一的思维逻辑分析问题二和问题三,就不用我写出来了吧!这就是多线程访问共享变量出现的问题,那么怎样才能解决这个问题哪?那么,线程同步该上场了,什么是线程同步?这个问题可以追溯的进程同步,点击超链接看看进程同步。
2、 现在给线程加锁试试,看下面的代码
public class Tick { public static int count = 100; public static object locker = new object(); public Tick(string name) { Thread t = new Thread(sell); t.Name = name; t.IsBackground = true; t.Start(); } public void sell() { while (count > 1) { lock (locker) { Tick.count--; Console.WriteLine(System.Threading.Thread.CurrentThread.Name + ",剩余{0}张!", count); } } } }
看看效果图
从图中可以看出,当线程t1和线程t2切换的时候,也就是1号售票口与2号售票切换的时候,余票的数据是正确的。简单的加了一个锁就可以变成这样了。那加锁的原理是什么哪?先看一张图。
2.1、每个对象其实在内存中都会分配一个对象头空间,其中最后2个bit 用来存放锁标识,当线程1给某一代码块加锁后,对象头标识里面数据就变成了1,在线程1没有执行完具体的操作之前,也就是线程1没有跳出上面的那个锁之前(跳出锁后标识自动变为0),线程2是不能访问被锁住的代码块的,因为线程2在访问这个代码块之前,也会访问这个锁对象,判断锁标识,此时锁标识是1,不是0,所以线程2不能进入被锁住的代码块。对应上面的问题一和问题2,也就是说当线程1每次循环执行过程中,只要每次循环没有执行完毕,锁就不会释放,其它线程就不会访问共享变量count,也就不会造成数据不同步问题了。
2.2、lock的本质:调用Monitor对象的Enter()方法来加锁,调用Monitor的Exit()方法解锁。
3、小结
将上面的问题总结一下,线程有同步也需要前提:同步需要两个或者两个以上的线程、多个线程使用的是同一个锁。线程同步的特点:即使获取了CPU的时间片,也无法执行。线程同步缺点:当线程相当多时,因为每个线程都会去判断同步上的锁,这很耗费资源,降低程序的运行效率。所以说,线程也不是越多越好,要适当着用。
相关文章推荐
- Android学习笔记之Serializable和Parcelable的区别
- [LeetCode 172] Factorial Trailing Zeroes
- Spring AOP(配置文件方式)
- hbase scan 的例子
- Win10/Win8.1自动维护功能怎么快速开启和关闭?
- bzoj2276 Temperature
- error at ::0 can't find referenced pointcut解决办法(转载)
- 不要放弃治疗
- 从零开始山寨Caffe·壹:仰望星空与脚踏实地
- bzoj1010 玩具装箱toy
- 卡夫卡作品研究
- 【BZOJ 1497】 [NOI2006]最大获利
- oracle中查询、禁用、启用、删除表外键
- 创业的第一百二十天
- 【BZOJ 1015】[JSOI2008]星球大战starwar
- BZOJ 4401: 块的计数|树分块
- Spring AOP(注解方式)
- 【BZOJ 1934】 [Shoi2007]Vote 善意的投票
- 想跳槽?先看什么是好工作
- 还原数据库,数据库提示正在还原中的处理办法