[MethodImpl(MethodImplOptions.Synchronized)]、lock(this)与lock(typeof(...))
对于稍微有点经验的.NET开发人员来说,倘若被问及如何保持线程同步,我想很多人都能说好好几种。在众多的线程同步的可选方式中,加锁无疑是最为常用的。如果仅仅是基于方法级别的线程同步,使用System.Runtime.CompilerServices.MethodImplAttribute无疑是最为简洁的一种方式。MethodImplAttribute可以用于instance method,也可以用于static method。当在某个方法上标注了MethodImplAttribute,并指定MethodImplOptions.Synchronized参数,可以确保在不同线程中运行的该方式以同步的方式运行。我们几天来讨论MethodImplAttribute(MethodImplOptions.Synchronized)和lock的关系。
一、提出结论
在进行讨论之前,我先提出下面3个结论:
[MethodImplAttribute(MethodImplOptions.Synchronized)]仍然采用加锁的机制实现线程的同步。 如果[MethodImplAttribute(MethodImplOptions.Synchronized)]被应用到instance method,相当于对当前实例加锁。 如果[MethodImplAttribute(MethodImplOptions.Synchronized)]被应用到static method,相当于当前类型加锁二、基于instance method的线程同步
为了验证我们上面提出的结论,我作了一个小小的例子。在一个console application中定义了一个class:SyncHelper,其中定义了一个方法Execute。打印出方法执行的时间,并休眠当前线程模拟一个耗时的操作:
5: Console.WriteLine("Excute at {0}", DateTime.Now);
6: Thread.Sleep(5000);
7: }
8: }
在入口Main方法中,创建SyncHelper对象,通过一个System.Threading.Timer对象实现每隔1s调用该对象的Execute方法:
5: SyncHelper helper = new SyncHelper();
6: Timer timer = new Timer(
7: delegate
8: {
9: helper.Execute();
10: }, null, 0, 1000);
11:
12: Console.Read();
13:
14: }
15: }
16:
由于Timer对象采用异步的方式进行调用,所以虽然Execute方法的执行时间是5s,但是该方法仍然是每隔1s被执行一次。这一点从最终执行的结果可以看出:
为了让同一个SyncHelper对象的Execute方法同步执行,我们在Execute方法上添加了如下一个MethodImplAttribute:
1: [MethodImpl(MethodImplOptions.Synchronized)]
2: public void Execute()
3: {
4: Console.WriteLine("Excute at {0}", DateTime.Now);
5: Thread.Sleep(5000);
6: }
从如下的输出结果我们可以看出Execute方法是以同步的方式执行的,因为两次执行的间隔正式Execute方法执行的时间:
在一开始我们提出的结论中,我们提到“如果[MethodImplAttribute(MethodImplOptions.Synchronized)]被应用到instance method,相当于对当前实例加锁”。说得直白一点:[MethodImplAttribute(MethodImplOptions.Synchronized)] lock(this)。我们可以通过下面的实验验证这一点。为此,在SyncHelper中定义了一个方法LockMyself。在此方法中对自身加锁,并持续5s中,并答应加锁和解锁的时间。
5: Console.WriteLine("Lock myself at {0}", DateTime.Now);
6: Thread.Sleep(5000);
7: Console.WriteLine("Unlock myself at {0}", DateTime.Now);
8: }
9: }
结合我们的第二个结论想想最终的输出会是如何。由于LockMyself方法是在另一个线程中执行,我们可以简单讲该方法的执行和Execute的第一个次执行看作是同时的。但是MethodImplAttribute(MethodImplOptions.Synchronized)]果真是通过lock(this)的方式实现的话,Execute必须在等待LockMyself方法执行结束将对自身的锁释放后才能得以执行。也就是说LockMyself和第一次Execute方法的执行应该相差5s。而输出的结果证实了这点:
三、基于static method的线程同步
讨论完再instance method上添加MethodImplAttribute(MethodImplOptions.Synchronized)]的情况,我们相同的方式来讨论倘若一样的MethodImplAttribute被应用到static方法,又会使怎样的结果。
我们先将Execute方法上的MethodImplAttribute注释掉,并将其改为static方法:
1: //[MethodImpl(MethodImplOptions.Synchronized)]
2: public static void Execute()
3: {
4: Console.WriteLine("Excute at {0}", DateTime.Now);
5: Thread.Sleep(5000);
6: }
毫无疑问,Execute方法将以1s的间隔异步地执行,最终的输出结果如下:
然后我们将对[MethodImpl(MethodImplOptions.Synchronized)]的注释取消:
1: [MethodImpl(MethodImplOptions.Synchronized)]
2: public static void Execute()
3: {
4: Console.WriteLine("Excute at {0}", DateTime.Now);
5: Thread.Sleep(5000);
6: }
最终的输出结果证实了Execute将会按照我们期望的那样以同步的方式执行,执行的间隔正是方法执行的时间:
我们回顾一下第三个结论:“如果[MethodImplAttribute(MethodImplOptions.Synchronized)]被应用到static method,相当于当前类型加锁”。为了验证这个结论,在SyncHelper中添加了一个新的static方法:LockType。该方法对SyncHelper tpye加锁,并持续5s中,在加锁和解锁是打印出当前时间:
4: {
5: Console.WriteLine("Lock SyncHelper type at {0}", DateTime.Now);
6: Thread.Sleep(5000);
7: Console.WriteLine("Unlock SyncHelper type at {0}", DateTime.Now);
8: }
9: }
如果基于static method的[MethodImplAttribute(MethodImplOptions.Synchronized)]是通过对Type进行加锁实现。那么通过Timer轮询的第一个Execute方法需要在LockType方法执行完成将对SyncHelper type的锁释放后才能执行。所以如果上述的结论成立,将会有下面的输出:
四、总结
对于加锁来说,锁的粒度的选择显得至关重要。在不同的场景中需要选择不同粒度的锁。如果选择错误往往会对性能造成很到的影响,严重时还会引起死锁。就拿[MethodImplAttribute(MethodImplOptions.Synchronized)]来说,如果开发人员对它的实现机制不了解,很有可能使它lock(this)或者lock(typeof(…))并存,造成方法得不到及时地执行。
最后说一句题外话,因为字符串驻留机制的存在,切忌对string进行加锁。
微信公众账号:大内老A
微博:www.weibo.com/artech
如果你想及时得到个人撰写文章以及著作的消息推送,或者想看看个人推荐的技术资料,可以扫描左边二维码(或者长按识别二维码)关注个人公众号(原来公众帐号蒋金楠的自媒体将会停用)。
本文版权归作者和博客园共有,欢迎转载,但未经作者同意必须保留此段声明,且在文章页面明显位置给出原文连接,否则保留追究法律责任的权利。 原文链接
浅析wait与synchronized 在Java语言中,Object作为顶级的父类中有一个wait()方法,我们都知道wait()跟notify()作为Java中的线程通信机制,但是你有没有想过:为什么wait方法是在Object中?它为什么不是在Thread中?
相关文章
- 端口号被占用:Identify and stop the process that‘s listening on port 10000 or configure this application...
- C知识点:指针函数【本质是一个函数,返回值是一个指针;ret *func(args, ...);】、函数指针【本质是一个指针,该指针的地址指向一个函数(指向代码段中函数入口地址的指针);用于回调函数】
- Cause: ...OutputBuildAction has been compiled by a more recent version of the Java Runtime (class file version 55.0), this version of the Java Runtime only recognizes class file versions up to 52.0
- vue3+ts 出现 import type declarations can only be used in TypeScript files/Type assertion expressions can only ...
- 30 个 IDEA 常用小技巧,应有尽有,让你的撸码效率直接起飞...
- 你还在用 Postman?IDEA REST Client 好用到爆,Postman 可以扔了...
- Stack Overflow 最火的一段代码竟然有 Bug...
- 普通2本,去过字节外包,到现在年薪25W+的测试开发,我的2年转行心酸经历...
- 自学180天,我从功能测试进阶到自动化测试了...
- 网瘾少年转行软件测试,月薪20k? 叛逆少年终归成长...
- 10年测试工程师的工作感悟,写给还在迷茫中的朋友...
- Mysql:mysqlslap:应用案例:持续补充 ...
- 数据库:Mysql中“select ... for update”排他锁分析
- MySQL 中 where id in (1,2,3,4,...) 的效率问题讨论
- 2023.2.2,周四【图神经网络 学习记录16】异构图Graph Embedding算法——GATNE(异构图多属性 多边 类型算法),不建议普通PC跑...PyCharm可能会崩
- MAVEN专题之八、大型Maven项目,快速按需任意构建,必备神技能!相知恨晚!...
- 解决Neither the JAVA_HOME nor the JRE_HOME environment variable is defined At least one of these...问题
- Rasa对话机器人连载二十三 第127课:Rasa对话机器人Debugging项目实战之教育领域项目微服务调用全生命周期调试二...
- MySQL This function has none of DETERMINISTIC, NO SQL...错误1418 的原因分析及解决方法 (转)
- 记录:remote: You are not allowed to push code to this project...【亲测有效】
- STL算法 | 置换求全排列 next_permutation、prev_permutation、is_permutation | ranges::is_permutation ...
- 高级数据结构 | 二叉树遍历 —非递归遍历,判断结点引用次数进行优先深度遍历 ...
- react native 中 ... 操作符的主要用途