Lock关键字解释:

lock 关键字将语句块标记为临界区,方法是获取给定对象的互斥锁,执行语句,然后释放该锁。 下面的示例包含一个 lock 语句。

lock 关键字可确保当一个线程位于代码的临界区时,另一个线程不会进入该临界区。 如果其他线程尝试进入锁定的代码,则它将一直等待(即被阻塞),直到该对象被释放。lock 关键字在块的开始处调用 Enter,而在块的结尾处调用 Exit。

在多线程中,每个线程都有自己的资源,但是代码区是共享的,即每个线程都可以执行相同的函数。这可能带来的问题就是几个线程同时执行一个函数,导致数据的混乱,产生不可预料的结果,因此我们必须避免这种情况的发生。

而在.NET中最好了解一下进程、应用域和线程的概念,因为Lock是针对线程一级的,而在.NET中应用域是否会对Lock起隔离作用,我的猜想是,即不在同一应用域中的线程无法通过Lock来中断;另外也最好能了解一下数据段、代码段、堆、栈等概念。

在C# lock关键字定义如下:

lock(expression) statement_block,其中expression代表你希望跟踪的对象,通常是对象引用。

如果你想保护一个类的实例,一般地,你可以使用this;如果你想保护一个静态变量(如互斥代码段在一个静态方法内部),一般使用类名就可以了。

而statement_block就是互斥段的代码,这段代码在一个时刻内只可能被一个线程执行。


二、简单解释一下执行过程

先来看看执行过程,代码示例如下:

private static object  ojb = new object();
lock(obj)
{
         //锁定运行的代码段
}  

假设线程A先执行,线程B稍微慢一点。线程A执行到lock语句,判断obj是否已申请了互斥锁,判断依据是逐个与已存在的锁进行object.ReferenceEquals比较(此处未加证实),如果不存在,则申请一个新的互斥锁,这时线程A进入lock里面了。

这时假设线程B启动了,而线程A还未执行完lock里面的代码。线程B执行到lock语句,检查到obj已经申请了互斥锁,于是等待;直到线程A执行完毕,释放互斥锁,线程B才能申请新的互斥锁并执行lock里面的代码。


三、Lock的对象选择问题

接下来说一些lock应该锁定什么对象。

    1、为什么不能lock值类型

比如lock(1)呢?lock本质上Monitor.Enter,Monitor.Enter会使值类型装箱,每次lock的是装箱后的对象。lock其实是类似编译器的语法糖,因此编译器直接限制住不能lock值类型。退一万步说,就算能编译器允许你lock(1),但是object.ReferenceEquals(1,1)始终返回false(因为每次装箱后都是不同对象),也就是说每次都会判断成未申请互斥锁,这样在同一时间,别的线程照样能够访问里面的代码,达不到同步的效果。同理lock((object)1)也不行。

    2、Lock字符串

那么lock("xxx")字符串呢?MSDN上的原话是:

锁定字符串尤其危险,因为字符串被公共语言运行库 (CLR)“暂留”。 这意味着整个程序中任何给定字符串都只有一个实例,就是这同一个对象表示了所有运行的应用程序域的所有线程中的该文本。因此,只要在应用程序进程中的任何位置处具有相同内容的字符串上放置了锁,就将锁定应用程序中该字符串的所有实例。

    3、MSDN推荐的Lock对象

通常,最好避免锁定 public 类型或锁定不受应用程序控制的对象实例。例如,如果该实例可以被公开访问,则 lock(this) 可能会有问题,因为不受控制的代码也可能会锁定该对象。这可能导致死锁,即两个或更多个线程等待释放同一对象。出于同样的原因,锁定公共数据类型(相比于对象)也可能导致问题。

而且lock(this)只对当前对象有效,如果多个对象之间就达不到同步的效果。

而自定义类推荐用私有的只读静态对象,比如:

private static readonly object obj = new object();

为什么要设置成只读的呢?这时因为如果在lock代码段中改变obj的值,其它线程就畅通无阻了,因为互斥锁的对象变了,object.ReferenceEquals必然返回false。

4、lock(typeof(Class))

与锁定字符串一样,范围太广了。


四、特殊问题:Lock(this)等的详细解释

在以前编程中遇到lock问题总是使用lock(this)一锁了之,出问题后翻看MSDN突然发现下面几行字:通常,应避免锁定 public 类型,否则实例将超出代码的控制范围。常见的结构 lock (this)、lock (typeof (MyType)) 和 lock ("myLock") 违反此准则:如果实例可以被公共访问,将出现C# lock this问题。如果 MyType 可以被公共访问,将出现 lock (typeof (MyType)) 问题。由于进程中使用同一字符串的任何其他代码将共享同一个锁,所以出现 lock(“myLock”) 问题。

来看看C# lock this问题:如果有一个类Class1,该类有一个方法用lock(this)来实现互斥:

publicvoidMethod2() 
{ 
    lock(this) 
    { 
        System.Windows.Forms.MessageBox.Show("Method2End"); 
    } 
}

如果在同一个Class1的实例中,该Method2能够互斥的执行。但是如果是2个Class1的实例分别来执行Method2,是没有互斥效果的。因为这里的lock,只是对当前的实例对象进行了加锁。

Lock(typeof(MyType))锁定住的对象范围更为广泛,由于一个类的所有实例都只有一个类型对象(该对象是typeof的返回结果),锁定它,就锁定了该对象的所有实例,微软现在建议,不要使用lock(typeof(MyType)),因为锁定类型对象是个很缓慢的过程,并且类中的其他线程、甚至在同一个应用程序域中运行的其他程序都可以访问该类型对象,因此,它们就有可能代替您锁定类型对象,完全阻止您的执行,从而导致你自己的代码的挂起。

锁住一个字符串更为神奇,只要字符串内容相同,就能引起程序挂起。原因是在.NET中,字符串会被暂时存放,如果两个变量的字符串内容相同的话,.NET会把暂存的字符串对象分配给该变量。所以如果有两个地方都在使用lock(“my lock”)的话,它们实际锁住的是同一个对象。到此,微软给出了个lock的建议用法:锁定一个私有的static 成员变量。

总结:

应避免锁定 public 类型,否则实例将超出代码的控制范围。常见的结构 lock (this)、lock (typeof (MyType)) 和 lock ("myLock") 违反此准则:

1)如果实例可以被公共访问,将出现 lock (this) 问题;

2)如果 MyType 可以被公共访问,将出现 lock (typeof (MyType)) 问题;

3)由于进程中使用同一字符串的任何其他代码将共享同一个锁,所以出现 lock("myLock") 问题;

最佳做法是定义 private 对象来锁定, 或 private static 对象变量来保护所有实例所共有的数据。


下面引入lock关键字的理论:

在应用程序中使用多个线程的一个好处是每个线程都可以异步执行。对于 Windows 应用程序,耗时的任务可以在后台执行,而使应用程序窗口和控件保持响应。对于服务器应用程序,多线程处理提供了用不同线程处理每个传入请求的能力。否则,在完全满足前一个请求之前,将无法处理每个新请求。 然而,线程的异步特性意味着必须协调对资源(如文件句柄、网络连接和内存)的访问。否则,两个或更多的线程可能在同一时间访问相同的资源,而每个线程都不知道其他线程的操作。结果将产生不可预知的数据损坏。 对于整数数据类型的简单操作,可以用 Interlocked 类的成员来实现线程同步。对于其他所有数据类型和非线程安全的资源,只有使用本主题中的结构才能安全地执行多线程处理。

lock 关键字可以用来确保代码块完成运行,而不会被其他线程中断。这是通过在代码块运行期间为给定对象获取互斥锁来实现的。

提供给 lock 关键字的参数必须为基于引用类型的对象,该对象用来定义锁的范围。在上例中,锁的范围限定为此函数,因为函数外不存在任何对该对象的引用。如果确实存在此类引用,锁的范围将扩展到该对象。严格地说,提供给 lock 的对象只是用来唯一地标识由多个线程共享的资源,所以它可以是任意类实例。然而,实际上,此对象通常表示需要进行线程同步的资源。例如,如果一个容器对象将被多个线程使用,则可以将该容器传递给 lock,而 lock 后面的同步代码块将访问该容器。只要其他线程在访问该容器前先锁定该容器,则对该对象的访问将是安全同步的。

通常,最好避免锁定 public 类型或锁定不受应用程序控制的对象实例。例如,如果该实例可以被公开访问,则 lock(this) 可能会有问题,因为不受控制的代码也可能会锁定该对象。这可能导致死锁,即两个或更多个线程等待释放同一对象。出于同样的原因,锁定公共数据类型(相比于对象)也可能导致问题。锁定字符串尤其危险,因为字符串被公共语言运行库 (CLR)“暂留”。 这意味着整个程序中任何给定字符串都只有一个实例,就是这同一个对象表示了所有运行的应用程序域的所有线程中的该文本。因此,只要在应用程序进程中的任何位置处具有相同内容的字符串上放置了锁,就将锁定应用程序中该字符串的所有实例。因此,最好锁定不会被暂留的私有或受保护成员。某些类提供专门用于锁定的成员。例如,Array 类型提供 SyncRoot。许多集合类型也提供 SyncRoot。

实例代码:

/*
该实例是一个线程中lock用法的经典实例,使得到的balance不会为负数
同时初始化十个线程,启动十个,但由于加锁,能够启动调用WithDraw方法的可能只能是其中几个
作者:http://hi.baidu.com/jiang_yy_jiang
*/
using System;
 
namespace ThreadTest29
{
    class Account
    {
        private Object thisLock = new object();
        int balance;
        Random r = new Random();
 
        public Account(int initial)
        {
            balance = initial;
        }
 
        int WithDraw(int amount)
        {
            if (balance < 0)
            {
                throw new Exception("负的Balance.");
            }
            //确保只有一个线程使用资源,一个进入临界状态,使用对象互斥锁,10个启动了的线程不能全部执行该方法
            lock (thisLock)
            {
                if (balance >= amount)
                {
                    Console.WriteLine("----------------------------:" + System.Threading.Thread.CurrentThread.Name + "---------------");
                    
                    Console.WriteLine("调用Withdrawal之前的Balance:" + balance);
                    Console.WriteLine("把Amount输入Withdrawal     :-" + amount);
                    //如果没有加对象互斥锁,则可能10个线程都执行下面的减法,加减法所耗时间片段非常小,可能多个线程同时执行,出现负数。
                    balance = balance - amount;
                    Console.WriteLine("调用Withdrawal之后的Balance :" + balance);
                    return amount;
                }
                else
                {
                    //最终结果
                    return 0;
                }
            }
        }
        public void DoTransactions()
        {
            for (int i = 0; i < 100; i++)
            {
                //生成balance的被减数amount的随机数
                WithDraw(r.Next(1, 100));
            }
        }
    }
 
    class Test
    {
        static void Main(string[] args)
        {
            //初始化10个线程
            System.Threading.Thread[] threads = new System.Threading.Thread[10];
            //把balance初始化设定为1000
            Account acc = new Account(1000);
            for (int i = 0; i < 10; i++)
            {
                System.Threading.Thread t = new System.Threading.Thread(new System.Threading.ThreadStart(acc.DoTransactions));
                threads[i] = t;
                threads[i].Name = "Thread" + i.ToString();
            }
            for (int i = 0; i < 10; i++)
            {
                threads[i].Start();
            }
            Console.ReadKey();
        }
    }
}

原文转载地址:http://www.cnblogs.com/promise-7/articles/2354077.html

还有一些知识,感觉现在没有用,就没有转载。

转载于:https://www.cnblogs.com/stemon/p/4201362.html

[C#参考]锁定lock相关推荐

  1. oracle锁住用户有什么用,oracle 用户user锁定lock如何知道是什么原因导致的

    问题 数据库用户被锁了,如何查看什么原因导致被锁的呢 结论 1,dba_users是由底层表user$,profile$,profname$相关表构成,当然还有ts$ 2,dba_users与账户锁定 ...

  2. oracle 用户被锁在sga,Oracle 用户user锁定lock如何知道是什么原因导致的

    问题 Oracle数据库用户被锁了,如何查看什么原因导致被锁的呢 结论 1,dba_users是由底层表user$,profile$,profname$相关表构成,当然还有ts$ 2,dba_user ...

  3. SQL语法——表锁定(LOCK、UNLOCK)

    MySQL 表锁定 锁是与表关联的标志.MySQL允许客户端会话显式获取表锁,以防止其他会话在特定时间段内访问同一个表.客户端会话只能为自己获取或释放表锁.它无法获取或释放其他会话的表锁. CREAT ...

  4. oracle解锁system密码,Oracle System密码忘记 密码修改、删除账号锁定lock

    运行cmd命令行 录入 sqlplus /nolog 无用户名登录 conn /as sysdba 连接到数据本地数据 alter user system identified by password ...

  5. oracle 账户 锁定 密码忘记了,Oracle System密码忘记 密码修改、删除账号锁定lock

    运行cmd命令行 录入 sqlplus /nolog  无用户名登录 conn /as sysdba  连接到数据本地数据 alter user system identified by passwo ...

  6. oracle修改用户system密码忘记,Oracle System密码忘记 密码修改、删除账号锁定lock (转) - mano...

    运行cmd命令行 录入 sqlplus /nolog  无用户名登录 conn /as sysdba  连接到数据本地数据 alter user system identified by passwo ...

  7. Vue目录结构与Yarn.lock 的版本锁定

    参考:Vue目录结构与Yarn.lock 的版本锁定 - 知乎 Vue目录结构与Yarn.lock 的版本锁定 一.Vue3.0目录结构图总览 刚看到vue的目录,一开始不知道该目录是什么含义,目录里 ...

  8. mysql udf limit_锁定服务UDF接口

    锁定服务UDF接口 本节介绍如何使用锁定服务用户定义功能(UDF)接口.要改为使用C语言接口,请参见"锁定服务C接口".有关锁定服务接口的一般特征,请参见"锁定服务&qu ...

  9. 多线程访问共同的代码或者对象:lock避免出错

    现在写程序多线程是不可避免的,而且经常会出现多线程访问共同资源的情况.多线程对共同资源的访问,往往会造成数据的混乱和不可预料的结果,因此一般需要加锁访问进行互斥访问.加锁就需要用到lock关键字.所谓 ...

最新文章

  1. C++中std::reverse和std::reverse_copy的使用
  2. Hadoop 之父趣事:用儿子的大象玩偶为大数据项目命名
  3. BZOJ2831(小强的金字塔系列问题--区域整点数求法)
  4. 留学计算机Ps模板,留学ps怎么写?出国留学ps模板
  5. oracle数据库中的系统自带表情_oracle 系统自带几个常用函数
  6. JAVA基础系列:反射
  7. android 开发对gif解码(适配android 4.2、4.3、4.4版本)
  8. eax ...edi esp ebp寄存器简介(转)
  9. 字符流读取的全部方法
  10. Ruby类的创建与使用
  11. java游戏开发教程_JAVA快速开发游戏代码实现 aide教程
  12. 【亲测可用】彻底解决Google谷歌地球启动无法连接到登录服务器、无法启动问题
  13. kali破解压缩包密码
  14. 准备了个freyja实例项目(单数据源版)
  15. 大数据血缘分析系统设计
  16. 机器学习-朴素贝叶斯(高斯、多项式、伯努利)
  17. Python中的BMI指数
  18. MATLAB 函数大全
  19. 微型计算机曾经使用过的字长,自考《计算机应用基础》模拟试题七
  20. uni-app前端H5页面底部内容被tabbar遮挡的问题解决方案

热门文章

  1. oracle 作业 断开原因,解惑 | Oracle JOB 异常中断原因分析
  2. caddy php sock,Caddy环境下一些Web应用程序的配置参考
  3. 天龙八部3d最新服务器,天龙八部3DIOS正版服务器整合互通公告
  4. Linux怎么创建date文件,Linux下手工创建oracle database
  5. echarts geo地图示例_干货|Pyecharts绘制好看的交互式地图教程
  6. mac php 连接mysql数据库_Mac环境下php操作mysql数据库的方法分享_PHP教程
  7. 上如何刻字_校园石阶上被人刻了1700多个字?!这次网友却说好
  8. Hankson的趣味题 解题记录
  9. 20175316 盛茂淞 实验一 Java开发环境的熟悉
  10. Maven整合SSM测试