您的位置:首页 > 其它

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

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

        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的时间片,也无法执行。线程同步缺点:当线程相当多时,因为每个线程都会去判断同步上的锁,这很耗费资源,降低程序的运行效率。所以说,线程也不是越多越好,要适当着用。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: