- 📢欢迎点赞 :👍 收藏 ⭐留言 📝 如有错误敬请指正,赐人玫瑰,手留余香!
- 📢本文作者:由webmote 原创
- 📢作者格言:新的征程,我们面对的不仅仅是技术还有人心,人心不可测,海水不可量,唯有技术,才是深沉黑夜中的一座闪烁的灯塔 !
序言
多线程下的变量访问,就如同脚踏几只船的海王,在其精细的时间管理下安排每一个女朋友约会,一不小心,就很可能打翻友谊的小船,彻底坠入无尽的大海深处…
而为了让各位亲爱的猿们,在约会对象之间横跳的时候,能优雅的控制住频率,编程语言引入了多个关键字和对象类完成相关操作。
让我们逐个看看,这些概念都能完成什么样的奇葩事件吧!
1. Volatile 修饰符关键字
volatile
关键字通常被用来表示一个字段的值很可能被多个线程修改,因此在编译器(VS)编译时不要进行优化,也不被缓存在编译器或硬件寄存器里。
volatile
关键字,确保每次读取和写入时,其值都是直接从内存中拿出来的,避免任何的优化和缓存。
被volatile
关键字标识的信息,就如同海王的A女友信息,每次海王想知道A女友的信息时,都显示的是A的最新信息,而不是从其他人打探的过时信息。有了第一手的信息,才能最大程度的避免不慎翻船。
让我们来个模拟例子吧,由于编译器的优化,准备这个例子着实不易。
//让我们在.net6下测试下...
Console.WriteLine("开始测试...");
var test = new Test();
new Thread(delegate () { Thread.Sleep(500); test.foo = 255; }).Start();
while (test.foo != 255) ;
Console.WriteLine("不好了,A女友正在抵达战场!");
Console.ReadLine();
public class Test
{
public int foo = 0;
}
如果你运行在Debug版本下,这时候你是可以收到A女友的抵达信息的。但是一旦你发布成Release,这个时候,命运的齿轮开始转动,你忽然收不到重要的抵达信息了,随着时间滴答滴答流动,危险的气息扑面而至。
你也试试看,切换到Release版本,按Ctrl+F5, 界面如下:
这个时候,volatile
关键字的重要性就体现出来了,我们修改下如下信息:
public class Test
{
public volatile int foo = 0;
}
看吧,一个volatile
,就救了你一条命。
volatile
的使用注意事项:
volatile
关键字通常用于多线程应用程序中,用于处理由多个线程同时访问的共享字段。volatile
不用于同步;它仅确保单个读取和写入操作的可见性和原子性。如果需要同步来强制执行顺序或互斥,请考虑使用其他同步机制,如lock
,Monitor
,Semaphore
。- 在多线程方案中处理共享数据时,通常建议使用
lock
关键字或其他原子操作类,因为仅使用volatile
关键字可能不足以满足复杂的同步要求。 - volatile`关键字用于字段修饰,一般常用的是整型、布尔、指针,当然还有引用类型(一般指地址)
一般关闭线程的布尔值是最佳使用场景。
单例的双重检查锁场景也是有用的,例如:
public class Singleton {
private static volatile Singleton _instance = null;
private static Object _locker = new Object();
public static Singleton GetSingleValue()
{
if (_instance == null)
{
lock(_locker)
{
if (_instance == null)
{ _ instance = new Singleton(); }
}
}
return _ instance;
}
当然,有更简单的写法,那就是利用Lazy
类
public class Singleton
{
private static readonly Lazy<Singleton> _instance
= new Lazy<Singleton>(() => new Singleton());
private Singleton()
{
}
public static Singleton Instance
{
get
{
return _instance.Value;
}
}
}
2. Lock 锁,锁住要锁的人
锁lock
,是最好用的保护机制之一了。 锁住资源,让其他线程都在后面排队,这样就不会撞到一块了。
话说,海王的日程表,必须有锁,没有锁的海王都死翘翘了。
这里是个简单的例子:
private object mylock = new object();
public int A {
get {
int result;
lock(mylock) {
result = mA;
}
return result;
}
set {
lock(mylock) {
mA = value;
}
}
}
作为演示,这个例子足够简单;作为深度学习,这个例子并不好。
大部分类的属性都不需要lock操作,使用 public DateTime CreatedTime{get;set;}
就已经足够了。因为基础类型都是原子操作的,因此没必要去锁定,除非你在get,set里有更复杂的操作。
因此,大可不必都增加上lock, 如果是多个线程访问,那么不妨增加上 volatile
,当然,属性没法直接增加,有需要多写代码了。
3.Interlocked 非锁的原子操作
锁是独一无二的,那么对于时间管理大师们,来说,这并不是好消息。
那么有什么其他办法,既能满足大师们同时多个骚操作,又能正常而及时的得到通知呢?那就不得不提Interlocked
了,经济实惠,的确是居家旅行必备良词。
public class NuclearPowerPlant
{
private long _meltdownIsHappening = 0;
public bool MeltdownIsHappeningRightNow
{
get
{
/* 锁定操作仅仅支持整型,那么我们使用它替换布尔。
*/
return Interlocked.Read(ref _meltdownIsHappening) == 1;
}
set
{
Interlocked.Exchange(ref _meltdownIsHappening, Convert.ToInt64(value));
}
}
}
这效率,嘎嘎的高。
注意 Interlocked.Increment(ref this.counter);
在实现上,等同于lock(this.locker) this. Counter++;
,不过效率吗,那是翻了几倍。可惜的是好东西总有限制。Interlocked
仅仅支持整数类型。
4. Synchronized 同步操作
Synchronized
关键字,总有点像从哪里抄过来的,因此,这个用法并不常见。
不过它的含义倒是很清晰,就是同一时刻仅允许一个线程访问。
代码如下:
public class Test
{
public volatile int foo = 0;
[MethodImpl(MethodImplOptions.Synchronized)]
public int Add(int a)
{
return foo + a;
}
}
MethodImpl(MethodImplOptions.Synchronized)
这个属性的实现,也很简单,就是粗暴的lock(this)
。
因此,不建议直接使用。
总结
哦哦哦,好像意犹未尽,不过时间有限,先到此为止吧。
👓都看到这了,还在乎点个赞吗?
👓都点赞了,还在乎一个收藏吗?
👓都收藏了,还在乎一个评论吗?