多线程同步与锁机制

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);

本篇就不详细讨论,下一篇再继续研究。


以上是关于多线程同步与锁机制的主要内容,如果未能解决你的问题,请参考以下文章

[ 转载 ] Java多线程-线程的同步与锁

Java多线程-线程的同步与锁

线程安全与锁

并发与多线程相关知识点梳理

并发与多线程相关知识点梳理

并发与多线程相关知识点梳理