1、什么是线程安全 

  线程安全是编程中的术语,指某个函数、函数库在多线程环境中被调用时,能够正确地处理多个线程之间的共享变量,使程序功能正确完成。
一般来说,线程安全的函数应该为每个调用它的线程分配专门的空间,来储存需要单独保存的状态(如果需要的话),不依赖于“线程惯性”,把多个线程共享的变量正确对待(如,通知编译器该变量为“易失(volatile)”型,阻止其进行一些不恰当的优化),而且,线程安全的函数一般不应该修改全局对象。(注:摘自https://www.baidu.gugeeseo.com/wiki/%E7%BA%BF%E7%A8%8B%E5%AE%89%E5%85%A8

  这里有两个前提:

  1、多线程环境

  2、线程直接共享变量

  在这个前提下,我们才可以讨论线程安全问题。那什么是线程安全?如果上边的文字还不能理解,请看以下示例:

  

     private int number = ;
public void TestVolatile()
{
for (var i = ; i < ; i++)
{
number++;
}
Console.WriteLine(number);
}

输出结果:100

这个结果和我们预期的一直,但这是单线程环境,现在我们使用多线程环境

     public void Test()
{
for (var i = ; i < ; i++)
{
TestVolatile();
}
} public void TestVolatile()
{
int number = ;
var tasks = new List<Task>();
for (var i = ; i < ; i++)
{
var task = Task.Run(() =>
{
number++;
});
tasks.Add(task);
}
Task.WaitAll(tasks.ToArray());
Console.WriteLine(number);
}

数据结果:

我们发现多线程环境下:

1、输出结果不是一个固定的值

2、输出结果和单线程环境结果不一致,和我们的预期不一致

通过上述两个示例,我们可以明确:示例一是线程安全的,示例二不是线程安全的

2、如何解决线程安全

  在思考如何解决线程安全问题前,我们先思考下示例二为什么会出现这种结果?

   首先,它肯定是执行了100次累加的,其次为什么结果会不是100呢?

   答案只有一个:脏读了,就是说:我同时多个线程读取到的number相同,大家都加了1在赋值给number,导致结果就是看上去只有一个加了1。

   所以根据这个问题,我们解决的思路就是多线程环境下对number++执行应该是单线程的,同时只能有一个线程在执行此段代码。

   所以想要解决线程安全问题,其实就是解决执行代码段原子性问题,确保同时只有一个线程在访问,那我们就说这段代码是线程安全的。在.NET中线程安全问题主要可以通过以下方式处理,它们各自适用的环境不一样,需要了解其机制酌情使用:

对象名/关键字 方法 说明 示例
lock(关键字)  

通过获取指定对象的排它锁,达到一段代码块线程安全的执行;

lock本身是个语法糖,底层是通过Monitor来实现的;

        private static object _LOCK = new object();

        public void Main()
{
Monitor.Enter(_LOCK);
try
{
//执行
}
catch { }
finally
{
Monitor.Exit(_LOCK);
}
}
        private static object _LOCK = new object();

        public void Main()
{
lock (_LOCK)
{ }
}
volatile(变量修饰符)  

volatile是C#中用于控制同步的关键字,其意义是针对程序中一些敏感数据,不允许多线程同时访问,保证数据在任何访问时刻,最多有一个线程访问,以保证数据的完整性,volatile是修饰变量的修饰符。

多个线程同时访问一个变量,CLR为了效率,允许每个线程进行本地缓存,这就导致了变量的不一致性。volatile就是为了解决这个问题,volatile修饰的变量,不允许线程进行本地缓存,每个线程的读写都是直接操作在共享内存上,这就保证了变量始终具有一致性。

        private volatile bool _isCompleteCalled;
static 构造函数  

将构造函数申明未静态的,这是利用的C# 类加载机制实现的。其只会在类第一次实例化时执行,优先级高于非静态构造函数;

 public class TestLock
{
static TestLock()
{
//在第一次实例化的时候,仅执行一次
}
}
Interlocked(为多个线程共享的变量提供原子操作) Add 对两个整数进行求和并用和替换第一个整数,上述操作作为一个原子操作完成。  
CompareExchange 比较两个对象 T 是否相等,如果相等,则替换第一个。  
Decrement 以原子操作的形式递减指定变量的值并存储结果  
Exchange  以原子操作的形式,将对象设置为指定的值并返回对原始对象的引用。  
Increment 以原子操作的形式递增指定变量的值并存储结果  
LazyInitializer(提供延迟初始化例程) EnsureInitialized 初始化具有类型的默认构造函数的目标引用类型,如果其尚未已初始化。  
Monitor(提供同步访问对象的机制) Enter 在指定对象上获取排他锁  
  Exit 释放指定对象上的排他锁  
  IsEntered 确定当前线程是否保留指定对象上的锁  
  Pulse 通知等待队列中的线程锁定对象状态的更改  
  PulseAll 通知所有的等待线程对象状态的更改  
  TryEnter 在指定的时间内尝试获取指定对象上的排他锁  
  Wait 释放对象上的锁并阻止当前线程,直到它重新获取该锁。 如果已用指定的超时时间间隔,则线程进入就绪队列。 此方法还指定是否在等待之前退出上下文的同步域(如果在同步上下文中)然后重新获取该同步域。  
System.Collections.Concurrent.BlockingCollection<T>   为实现 System.Collections.Concurrent.IProducerConsumerCollection`1 的线程安全集合提供阻塞和限制功能。  
System.Collections.Concurrent.ConcurrentBag<T>   表示对象的线程安全的无序集合。  
System.Collections.Concurrent.ConcurrentDictionary<TKey, TValue>   表示可由多个线程同时访问的键/值对的线程安全集合。  
System.Collections.Concurrent.ConcurrentQueue<T>   表示线程安全的先进先出 (FIFO) 集合。  
System.Collections.Concurrent.ConcurrentStack<T>   表示线程安全的后进先出 (LIFO) 集合。  

.NET多线程之线程安全,Lock(锁)、Monitor(同步访问)、LazyInitializer(延迟初始化)、Interlocked(原子操作)、static(静态)构造函数、volatile、的更多相关文章

  1. JAVA多线程(三) 线程池和锁的深度化

    github演示代码地址:https://github.com/showkawa/springBoot_2017/tree/master/spb-demo/spb-brian-query-servic ...

  2. Java多线程(一)——线程基础和锁锁锁

    目录 Java多线程(一) 一.线程的定义 二.Synchronize线程同步 三.偏向锁.自旋锁.重量级锁 四.volatile关键字 五.Compare And Swap无锁自旋优化技术和ABA版 ...

  3. 多线程同步与并发访问共享资源工具—Lock、Monitor、Mutex、Semaphore

    “线程同步”的含义   当一个进程启动了多个线程时,如果需要控制这些线程的推进顺序(比如A线程必须等待B和C线程执行完毕之后才能继续执行),则称这些线程需要进行“线程同步(thread synchro ...

  4. 线程高级篇-Lock锁实现生产者-消费者模型

    Lock锁介绍: 在java中可以使用 synchronized 来实现多线程下对象的同步访问,为了获得更加灵活使用场景.高效的性能,java还提供了Lock接口及其实现类ReentrantLock和 ...

  5. Java中的Lock锁

    Lock锁介绍: 在java中可以使用 synchronized 来实现多线程下对象的同步访问,为了获得更加灵活使用场景.高效的性能,java还提供了Lock接口及其实现类ReentrantLock和 ...

  6. 并发编程系列之Lock锁可重入性与公平性

    一.相似之处:Lock锁 vs Synchronized 代码块 Lock锁是一种类似于synchronized 同步代码块的线程同步机制.从Java 5开始java.util.concurrent. ...

  7. 多线程 - 线程同步锁(lock、Monitor)

    1. 前言 多线程编程的时候,我们不光希望两个线程间能够实现逻辑上的先后顺序运行,还希望两个不相关的线程在访问同一个资源的时候,同时只能有一个线程对资源进行操作,否则就会出现无法预知的结果. 比如,有 ...

  8. 重新想象 Windows 8 Store Apps (46) - 多线程之线程同步: Lock, Monitor, Interlocked, Mutex, ReaderWriterLock

    [源码下载] 重新想象 Windows 8 Store Apps (46) - 多线程之线程同步: Lock, Monitor, Interlocked, Mutex, ReaderWriterLoc ...

  9. C#多线程系列(2):多线程锁lock和Monitor

    1,Lock lock 原型 lock 编写实例 2,Monitor 怎么用呢 解释一下 示例 设置获取锁的时效 C# 中,可以使用 lock 关键字和 Monitor 类来解决多线程锁定资源和死锁的 ...

随机推荐

  1. Win8 Metro(C#)数字图像处理--2.53图像傅立叶变换

    原文:Win8 Metro(C#)数字图像处理--2.53图像傅立叶变换  [函数名称] 1,一维FFT变换函数         Complex[] FFT(Complex[] sourceDat ...

  2. SqlServer 无法为可更新的订阅设置发布服务器登录名 sp_link_publication

    原文:SqlServer 无法为可更新的订阅设置发布服务器登录名 sp_link_publication 没有截图: 创建可更新订阅,正常创建了发布,在订阅端创建订阅,最后一步提示完成,却出现了警告: ...

  3. DevExpress的xtraMessageBox汉化

    原文:DevExpress的xtraMessageBox汉化 项目使用的界面库是DevExpress 相当好用,不过里面弹出对话框XtraMessageBox的按钮都是英文的, 可能会对用户造成困扰, ...

  4. 微信小程序把玩(十三)progress组件

    原文:微信小程序把玩(十三)progress组件 进度条描述的是一种加载的状态,比如软件升级下载进度, 视频,图片下载进度- 主要属性: wxml <progress percent=" ...

  5. 零元学Expression Blend 4 &ndash; Chapter 43 如何指定Childwindow PopUp位置

    原文:零元学Expression Blend 4 – Chapter 43 如何指定Childwindow PopUp位置 有网友询问我有关Childwindow是否能指定弹出位置? 其实只要透过小小 ...

  6. Windows下获取逻辑cpu数量和cpu核数量

    代码可在Windows NT下正常运行 具体API说明请参照如下文档: GetLogicalProcessorInformation 点击打开链接 点击打开链接 点击打开链接 typedef BOOL ...

  7. C C++ Java C# JS编译、执行过程的原理入门分析

    C.C++是典型的编译型编程语言,编译链接后,点击则可执行. JS,解释型脚本语言,则不需要进行编译,直接解释执行. Java和C#则是所谓的高级语言,编译执行的方式做了很多处理, 尤其是C#,VS编 ...

  8. RocketMQ(1)-架构原理

    RocketMQ(1)-架构原理 RocketMQ是阿里开源的分布式消息中间件,跟其它中间件相比,RocketMQ的特点是纯JAVA实现:集群和HA实现相对简单:在发生宕机和其它故障时消息丢失率更低. ...

  9. scikit-learn杂记

    1.数据预处理 二值化 import numpy as np from sklearn import preprocessing X = np.array([[1., -1., 2.], [2., 0 ...

  10. WPF基于3D对象捕获鼠标点击事件

    ModelUIElment3DUIElment3D是.NET3.5新增的类,替代了ModelVisual3D.ModelVisual3D有2个派生类ModelUIElment3D和ContainerU ...