假设多个线程共享一个静态变量,如果让每个线程都执行相同的方法每次让静态变量自增1,这样的做法线程安全吗?能保证自增变量数据同步吗?本篇体验使用lock语句块和Interlocked类型方法保证自增变量的数据同步。

□ 线程不安全、数据不同步的做法

    class Program
    {
        static int sum = 0;
        static void Main(string[] args)
        {
            Stopwatch watch = new Stopwatch();
            watch.Start();
            Parallel.For(0, Environment.ProcessorCount, i =>
            {
                for (int j = 0; j < 100000000; ++j)
                {
                    AddOne();
                }
            });
            watch.Stop();
            Console.WriteLine("sum={0},用了{1}", sum, watch.Elapsed);
            Console.ReadKey();
        }

        static void AddOne()
        {
            sum++;
        }
    }

○ 变量sum是静态的,供所有线程共享
○ Parallel.For提供并行循环, Environment.ProcessorCount表示处理器的处理,如果有4个CPU,就做4组循环

我们发现,结果不是我们期望的400000000,也就是说,在这种情况下的静态变量自增不是线程安全的,换句话说,无法保证共享数据的同步。

□ 通过lock语句块保持数据同步


    class Program
    {
        static int sum = 0;
        private static readonly object o = new object();
        static void Main(string[] args)
        {
            Stopwatch watch = new Stopwatch();
            watch.Start();
            Parallel.For(0, Environment.ProcessorCount, i =>
            {
                for (int j = 0; j < 100000000; ++j)
                {
                    AddOne();
                }
            });
            watch.Stop();
            Console.WriteLine("sum={0},用了{1}", sum, watch.Elapsed);
            Console.ReadKey();
        }

        static void AddOne()
        {
            lock (o)
            {
                sum++;
            }

        }
    }


这一次,使用lock语句块得到了预期的结果。

□ 使用Interlocked保持数据同步

对于int或long类型变量的自增,并且保证类型安全,可以使用Interlocked类。提供的方法包括:

○ int Interlocked.Increment(ref int location),自增1
○ long Interlocked.Increment(ref long location),自增1
○ int Interlocked.Decrement(ref int location),自减1
○ long Interlocked.Decrement(ref long location),自减1
○ int Interlocked.Add(ref int location, int value),自增一个值
○ long Interlocked.Add(ref long location, long value),自增一个值

    class Program
    {
        static int sum = 0;
        static void Main(string[] args)
        {
            Stopwatch watch = new Stopwatch();
            watch.Start();
            Parallel.For(0, Environment.ProcessorCount, i =>
            {
                for (int j = 0; j < 100000000; ++j)
                {
                    AddOne();
                }
            });
            watch.Stop();
            Console.WriteLine("sum={0},用了{1}", sum, watch.Elapsed);
            Console.ReadKey();
        }

        static void AddOne()
        {
            Interlocked.Increment(ref sum);
        }
    }


使用Interlocked也能保证线程安全、数据同步,但耗时较长。

总结:
○ lock语句块和Interlocked类型方法都能保证自增变量的线程安全、数据同步
○ Interlocked类型方法只适用于int,long类型变量,有一定的局限性

线程系列包括:

线程系列01,前台线程,后台线程,线程同步

线程系列02,多个线程同时处理一个耗时较长的任务以节省时间

线程系列03,多线程共享数据,多线程不共享数据

线程系列04,传递数据给线程,线程命名,线程异常处理,线程池

线程系列05,手动结束线程

线程系列06,通过CLR代码查看线程池及其线程

线程系列07,使用lock语句块或Interlocked类型方法保证自增变量的数据同步

线程系列08,实现线程锁的各种方式,使用lock,Montor,Mutex,Semaphore以及线程死锁

线程系列09,线程的等待、通知,以及手动控制线程数量

线程系列10,无需显式调用线程的情形

线程系列07,使用lock语句块或Interlocked类型方法保证自增变量的数据同步的更多相关文章

  1. 线程系列08,实现线程锁的各种方式,使用lock,Montor,Mutex,Semaphore以及线程死锁

    当涉及到多线程共享数据,需要数据同步的时候,就可以考虑使用线程锁了.本篇体验线程锁的各种用法以及线程死锁.主要包括: ※ 使用lock处理数据同步※ 使用Monitor.Enter和Monitor.E ...

  2. 线程系列06,通过CLR代码查看线程池及其线程

    在"线程系列04,传递数据给线程,线程命名,线程异常处理,线程池"中,我们已经知道,每个进程都有一个线程池.可以通过TPL,ThreadPool.QueueUserWorkItem ...

  3. C# lock关键词/lock语句块、线程锁

    一.lock关键词说明 1. lock 关键字将语句块标记为临界区,方法是获取给定对象的互斥锁,执行语句,然后释放该锁. 2. lock 语句块锁定,功能等同于 Monitor.Enter(obj): ...

  4. lock语句的递归问题

    原文地址 前几天在网上闲逛,无意中看到有这么一道题及其答案,如下: 根据线程安全的相关知识,分析以下代码,当调用test方法时i>10时是否会引起死锁?并简要说明理由. ) { i--; tes ...

  5. C#线程系列讲座(5):同步技术之Monitor

    在上一讲介绍了使用lock来实现线程之间的同步.实际上,这个lock是C#的一个障眼法,在C#编译器编译lock语句时,将其编译成了调用Monitor类.先看看下面的C#源代码: public sta ...

  6. 左右 Java 于 finally 深度分析语句块

    首先,让我们来问你一个问题:finally 声明块将运行? 很多人认为 finally 语句块是一定要运行.其中还包括了一些非常有经验的 Java 程序猿.不幸的是,没有像很多像人们想象,对于这个问题 ...

  7. finally语句块

    finally语句块是搭配着try语句块出现的,也就说必须有try语句块才会有finally语句块,但是并不是try语句块都会搭配有finally语句块出现,我们常见的更多是try...catch.. ...

  8. synchronized(1)用法简介:修饰方法,修饰语句块

    注意: 同一个对象或方法在不同线程中才出现同步问题,不同对象在不同线程互相不干扰. synchronized方法有2种用法:修饰方法,修饰语句块 1.synchronized方法 是某个对象实例内,s ...

  9. Try-Catch-Finally语句块执行问题

    Try-Catch-Finally语句块执行问题 记录一个今天某公司的面试问题,其实我问题回答对了,但是面试官问我动手验证过没有,这还真没有,纯理论,被怼惨了,希望自己能变得更强大. Try-Catc ...

随机推荐

  1. 关于Eclipse连接sql server 2008的若干问题

    以下内容转自:https://www.cnblogs.com/skylarzhan/p/7619977.html Eclipse中使用SQL server 2008数据库 一.准备材料 要能够使用数据 ...

  2. Python学习笔记:import sys模块(argv、path、platform、exit)

    sys模块是Python标准库中自带的一个模块. sys模块包括了一组非常实用的服务,内含很多函数方法和变量,用来处理Python运行时配置以及资源,从而可以与当前程序之外的系统环境交互,如:Pyth ...

  3. 20165333 2017-2018-2《Java程序设计》课程总结

    一.每周作业链接汇总 1.预备作业一:我期望的师生关系 简要内容: 印象深刻的老师 我期望的师生关系 关于JAVA学习 2.预备作业二:学习基础和C语言学习基础 简要内容: 技能学习 C语言学习 关于 ...

  4. Linux密码策略-密码长度-密码复杂度

    1.设置密码长度 vim /etc/pam.d/system-authpassword requisite pam_cracklib.so try_first_pass retry=3 minlen= ...

  5. 在MVC中如何使用vs2013编译调试razor cshtml

    打开mvc项目的csproj文件: <MvcBuildViews>false</MvcBuildViews> 改为 <MvcBuildViews>true</ ...

  6. OneNote无法同时设置中英文字体设置解决办法

    如果你是一位OneNote老用户,无论是2003.2007还是2010或者最新的2013版本,都一直存在一个Bug,就是无法同时设置中英文字体(比如在Word中就可以分别设置不同的).我搜了一下,在微 ...

  7. ABP zero 3.2 发布

    v3.2.0 (2017-03-07) Common Tenant based UI customizations (allow tenants to upload custom CSS and lo ...

  8. 【AtCoder】AGC011 D - Half Reflector

    题解 大意是n个管子排成一排,每个管子有两种状态,A状态是从某个方向进去,从原方向出来,B状态是从某个方向进去,从另一个方向出来 球经过一个A状态的管子这个管子会立刻变成B状态,经过一个B状态的管子会 ...

  9. awk 提取列

    awk '{OFS="";print(substr($0,1,6),substr($0,74,18),substr($0,15,3),substr($0,18,8))}' inpu ...

  10. 复习一下xml(c)

    简单介绍 Using System.Xml; XMLDocument xml=new XmlDocument();xml.Load(path);//初始化一个实例 xml.Load(HttpConte ...