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

假设多个线程共享一个静态变量,如果让每个线程都执行相同的方法每次让静态变量自增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语句块保持数据同步

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类型方法保证自增变量的数据同步

时间: 2024-10-14 00:37:53

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

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

当涉及到多线程共享数据,需要数据同步的时候,就可以考虑使用线程锁了.本篇体验线程锁的各种用法以及线程死锁.主要包括: ※ 使用lock处理数据同步※ 使用Monitor.Enter和Monitor.Exit处理数据同步※ 使用Mutex处理进程间数据同步※ 使用Semaphore处理数据同步※ 线程死锁 □ 使用lock处理数据同步 假设有一个类,主要用来计算该类2个字段的商,在计算商的方法之内让被除数自减,即被除数有可能为零.使用lock语句块保证每次只有一个线程进入该方法. class Th

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

当一个线程直到收到另一个线程的通知才执行相关的动作,这时候,就可以考虑使用"事件等待句柄(Event Wait Handles)".使用"事件等待句柄"主要用到3个类: AutoResetEvent, ManualResetEvent以及CountdownEvent(.NET 4.0以后才有).本篇包括: ※ 一个线程等待另一个线程的通知※ 2个线程互相通知等待※ 一个线程等待队列中的多个任务通知※ 手动控制线程的数量 □ 一个线程等待另一个线程的通知 最简单的情景

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

通常,我们会通过线程的构造函数先创建线程再使用线程.而实际上,.NET中有些类提供的方法,其内部就是使用多线程处理的.一些封装了多线程.异步处理方法的类都符合了"事件驱动异步模式(event-based asynchronous pattern)".以System.ComponentModel下的BackgroundWorker类来说,该类就符合这种模式. BackgroundWorker类属性:WorkerSupportsCancellation:设置为true表示允许取消Worke

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

多线程编程,有时希望每个线程的数据相互隔离互不影响,有时却希望线程间能共享数据,并保持同步.本篇体验多线程共享和不共享数据. □ 多线程不共享数据 对于多线程,CLR到底是怎样为它们分配内存栈空间呢?是"一个萝卜一个坑",每个线程都有自己的栈空间:还是"大树底下好乘凉",所有的线程共享同一个栈空间? 我们让2个线程执行相同的静态方法,用到相同的变量,通过打印变量来求证多线程栈空间的分配情况. class Program { static void Main(stri

笔试题-同线程Lock语句递归不会死锁

笔试题-同线程Lock语句递归不会死锁前几天在网上闲逛,无意中看到有这么一道题及其答案,如下: 根据线程安全的相关知识,分析以下代码,当调用test方法时i>10时是否会引起死锁?并简要说明理由. public void test(int i) { lock(this) { if (i > 10) { i--; test(i); } } } 答:不会发生死锁,(但有一点int是按值传递的,所以每次改变的都只是一个副本,因此不会出现死锁.但如果把int换做一个object,那么死锁会发生) 当我

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

在控制台应用程序集中,Main方法开始的是一个线程.如果要再创建线程,需要用到System.Threading这个命名空间. □ 创建第一个线程 using System; using System.Threading; namespace ConsoleApplication4 { class Program { static void Main(string[] args) { var thread = new Thread(DoSth); thread.Start(); } static

关于 Java 中 finally 语句块的深度辨析

可不能小看这个简单的 finally,看似简单的问题背后,却隐藏了无数的玄机.接下来我就带您一步一步的揭开这个 finally 的神秘面纱. 问题分析 首先来问大家一个问题:finally 语句块一定会执行吗? 很多人都认为 finally 语句块是肯定要执行的,其中也包括一些很有经验的 Java 程序员.可惜并不像大多人所认为的那样,对于这个问题,答案当然是否定的,我们先来看下面这个例子. 清单 1. 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 1

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

首先,让我们来问你一个问题:finally 声明块将运行? 很多人认为 finally 语句块是一定要运行.其中还包括了一些非常有经验的 Java 程序猿.不幸的是,没有像很多像人们想象,对于这个问题,当然,答案是否定的,让我们来看看这个样品之后. 清点1 public class Test { public static void main(String[] args) { System.out.println("return value of test(): " + test())

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

在上一讲介绍了使用lock来实现线程之间的同步.实际上,这个lock是C#的一个障眼法,在C#编译器编译lock语句时,将其编译成了调用Monitor类.先看看下面的C#源代码: Code highlighting produced by Actipro CodeHighlighter (freeware) http://www.CodeHighlighter.com/ -->public static void MyLock() {     lock (typeof(Program))