c# 多线程(二) 多线程的安全

来源:互联网 发布:网络骑士txt 编辑:程序博客网 时间:2024/05/16 13:01

      当多个线程使用公共代码、属性时,会出现A线程刚通过一系列的算法后得出结果,刚要输出结果的时候,却被B线程修改,此时输出的结果显然不正确。于是我们就出现了锁,即线程的同步,多个线程同时执行一段公共的代码,只允许一个线程执行,其他线程等待。接下来介绍线程的2种常用锁。

一、lock

lock(object):会锁定该段代码(object必须是引用类型,不同于Monitor,后面有介绍monitor)。
lock(this):锁定当前实例的代码,多个实例互补影响。
在静态方法中Lockobject)不允许锁定非静态方法的字段属性,在静态方法中没有this,所以在多线程中在锁定代码段值得注意这一点。

控制台例子:

using System;using System.Collections.Generic;using System.Text;using System.Threading;namespace MyDemo_Lock{    //公共函数类    public class Person    {        static string iAge = "0";//年龄        string name = string.Empty;//名字        public Person(string Name)        {            name = Name;        }        public void WriteInfo(object age)        {            lock (this)            //lock (iAge)            {                for (int a = 0; a < 5; a++)//为了更直接的看清执行的步骤设置的                   {                }                iAge = age.ToString();                Console.WriteLine(string.Format("{0}今年{1}岁了,快成大人了!", name, iAge));            }                   }    }    class Program    {        static void Main(string[] args)        {                      Person person = new Person("jim");            Person person1 = new Person("Tom");            Thread jim = new Thread(new ParameterizedThreadStart(person.WriteInfo));            Thread tom = new Thread(new ParameterizedThreadStart(person1.WriteInfo));            jim.Start("5");             tom.Start("10");            jim.Join();            tom.Join();            Console.ReadKey();        }    }}


当使用2个实例时,在调试中可以看到:lock(object)会把锁定的代码块,供先到的线程使用,使用完后下一个线程才可以使用;lock(this)会互不干涉。

 

 二、静态类Monitor

lock与Monitor方式一样
lock(obj){...}就等同于try{Monitor.Enter()...}finally{Monitor.Exit()}
控制台例子:

using System;using System.Collections.Generic;using System.Text;using System.Threading;namespace MyDemo_Lock{    //公共函数类    public class Person    {        static string iAge = "0";//年龄        string name = string.Empty;//名字        //bool IsDisplaySister = false;//是开始显示        public Person(string Name)        {            name = Name;        }        public void WriteInfo(object age)        {            try            {                Monitor.Enter(this);                //Monitor.Enter(iAge);                for (int a = 0; a < 5; a++)//为了更直接的看清执行的步骤设置的                   {                }                //iAge = age.ToString();//Monitor在锁定区域内不能对被锁对象的值进行修改,运行时抱错“从不同步的代码块中调用了对象同步方法”                  Console.WriteLine(string.Format("{0}今年{1}岁了,快成大人了!", name, age));            }            finally            {                //Monitor.Exit(this);                Monitor.Exit(iAge);            }             }    }    class Program    {        static void Main(string[] args)        {            Person person = new Person("jim");            Person person1 = new Person("Tom");            Thread jim = new Thread(new ParameterizedThreadStart(person.WriteInfo));            Thread tom = new Thread(new ParameterizedThreadStart(person1.WriteInfo));            jim.Start("5");            tom.Start("10");            jim.Join();            tom.Join();            Console.ReadKey();       }    }}

本示例与lock示例基本一致,Monitor.Enter(obj),obj可以为值类型。
在使用实例化2个实例时,使用Monitor.Enter(obj),Monitor在锁定区域内不能对被锁对象的值进行修改,在释放时会报“从不同步的代码块中调用了对象同步方法”,使用Monitor.Enter(this)可以避免。


线程调用Monitor.Enter()方法锁定一个对象时,这个对象就归它所有了,其它线程想要访问这个对象,只有等待它使用Monitor.Exit()方法释放锁。为了保证线程最终都能释放锁,你可以把Monitor.Exit()方法写在try-finally结构中的finally代码块里。

对于任何一个被锁定的对象,内存中都保留下面3个信息:
1、现在持有锁的线程的引用;
2、一个预备队列,队列中保存了已经准备好获取锁的线程;
3、一个等待队列,队列中保存着当前正在等待这个对象状态改变的队列的引用。

拥有对象锁的线程准备释放锁时,使用Monitor.Pulse()方法通知等待队列中的第一个线程,该线程被调到预备队列,当拥有锁的线程释放时,该预备队列的线程获得该对象锁。

主线程启动两个线程后,这两个线程哪个先获得对象锁是未知的。
示例:姐姐通过弟弟的年龄来计算自己的年龄,来显示弟弟和姐姐的年龄信息。WriteInfo保存弟弟的年龄,ReadInfo显示姐姐的年龄,为了防止姐姐先获得对象锁,无法正常显示姐姐的年龄。

using System;using System.Collections.Generic;using System.Text;using System.Threading;namespace MyDemo_Lock{    //公共函数类    public class Person    {       static string iAge = "0";//年龄        string name = string.Empty;//名字        bool IsDisplaySister = false;//弟弟的年龄是否保存,true:保存        public Person(string Name)       {            name = Name;       }       public void WriteInfo(object age)       {            Thread.Sleep(1000);            try            {                Monitor.Enter(this);                if (IsDisplaySister)                {                    Monitor.Wait(this);                }                iAge = age.ToString();//Monitor在锁定区域内不能对被锁对象的值进行修改,运行时抱错“从不同步的代码块中调用了对象同步方法”                Console.WriteLine(string.Format("{0}今年{1}岁了,快成大人了!", name, iAge));                IsDisplaySister = true;                Monitor.Pulse(this);//通知等待的线程,锁定对象的状态发生改变,该线程被调到预备队列            }            finally            {                Monitor.Exit(this);            }       }       public void ReadInfo()       {            try            {                Monitor.Enter(this);                if (!IsDisplaySister)                {                    Monitor.Wait(this);//释放排它锁,挂起该线程,直到它重新获取该锁。                  }                iAge = (int.Parse(iAge) + 2).ToString();                Console.WriteLine(string.Format("{0}的姐姐今年{1}岁了,快成大人了!", name, iAge));                IsDisplaySister = false;                Monitor.Pulse(this);            }            finally            {                Monitor.Exit(this);            }       }    }    class Program    {        static void Main(string[] args)        {            Person person = new Person("jim");            Thread jim = new Thread(new ParameterizedThreadStart(person.WriteInfo));            Thread jimSister = new Thread(new ThreadStart(person.ReadInfo));            jimSister.Start();            jim.Start("5");            jimSister.Join();            jim.Join();            Console.ReadKey();        }    }}

上面的示例也可以用lock锁定代码块,可以用Monitor来限制,因为Monitor是静态类,当姐姐先获取对象锁时Monitor来挂起当前线程,弟弟的年龄保存后,释放线程,姐姐获取该锁后继续运行。

注:有不妥之处,请指教!共同进步,谢谢

原创粉丝点击