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

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 = 0;
        public void TestVolatile()
        {
            for (var i = 0; i < 100; i++)
            {
                number++;
            }
            Console.WriteLine(number);
        }

输出结果:100

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

     public void Test()
        {
            for (var i = 0; i < 10; i++)
            {
                TestVolatile();
            }
        }

        public void TestVolatile()
        {
            int number = 0;
            var tasks = new List<Task>();
            for (var i = 0; i < 100; 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) 集合。 

转载于:https://www.cnblogs.com/wlhai/p/10821388.html

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值