《线程》——多线程同步实例剖析

来源:互联网 发布:发改委大数据专项2016 编辑:程序博客网 时间:2024/05/22 14:06

         线程这个名词我们在学习操作系统的时候就接触过了,线程又称为轻量级进程,那进程是什么哪?大家可以跟随我的超链接看一下百度百科的解释。

        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的时间片,也无法执行。线程同步缺点:当线程相当多时,因为每个线程都会去判断同步上的锁,这很耗费资源,降低程序的运行效率。
所以说,线程也不是越多越好,要适当着用。


2 0
原创粉丝点击