• List是线程安全的吗?如果不是该怎么办呢?安全的List对性能的影响有多大呢?


    测试条件:

    开启2个并行执行任务,往同一个list对象写入值

    测试代码:

    static int maxNum = 1000000;
    
            static List<int> list = new List<int>();
    
            static void Main(string[] args)
    
            {
    
                //迭代次数
    
                int iterationNum = 3;
    
                CodeTimer.Initialize();
    
                CodeTimer.Time("List是否是线程安全的呢?", iterationNum, new Action(ListIsThreadSafe));
    
                //Console.Write(sbIsThreadSafe.ToString());
    
                Console.Read();
    
            }
    
     
    
            private static void ListIsThreadSafe()
    
            {
    
                Parallel.For(1, maxNum / 2, (i) =>
    
                {
    
                    list.Add(i);
    
                });
    
                Parallel.For(maxNum / 2 + 1, maxNum, (i) =>
    
                {
    
                    list.Add(i);
    
                });
    
            }
    View Code

    测试结果:

     

    测试结论:

    之所以会造成以上的结果是因为list对象不是线程安全。那该怎么办呢? 

    这时我们需要使用System.Collections.Concurrent命名空间下的类型来用于并行循环体内。

    说明

    BlockingCollection<T>

    为实现 IProducerConsumerCollection<T> 的线程安全

    集合提供阻止和限制功能。

    ConcurrentBag<T>

    表示对象的线程安全的无序集合。

    ConcurrentDictionary<TKey, TValue>

    表示可由多个线程同时访问的键值对的线程安全集合。

    ConcurrentQueue<T>

    表示线程安全的先进先出 (FIFO) 集合。

    ConcurrentStack<T>

    表示线程安全的后进先出 (LIFO) 集合。

    OrderablePartitioner<TSource>

    表示将一个可排序数据源拆分成多个分区的特定方式。

    Partitioner

    提供针对数组、列表和可枚举项的常见分区策略。

    Partitioner<TSource>

    表示将一个数据源拆分成多个分区的特定方式。

    我们再来进行一次测试。

    测试代码:

    static int maxNum = 1000000;   
    
            static ConcurrentQueue<int> safeList = new ConcurrentQueue<int>();
    
            static void Main(string[] args)
    
            {
    
                //迭代次数
    
                int iterationNum = 3;
    
                CodeTimer.Initialize();
    
                CodeTimer.Time("ConcurrentQueue是否是线程安全的呢?", iterationNum, new Action(ListIsThreadSafe));
    
                //Console.Write(sbIsThreadSafe.ToString());
    
                Console.Read();
    
            }
    
     
    
            private static void ListIsThreadSafe()
    
            {
    
                Parallel.For(1, maxNum / 2, (i) =>
    
                {
    
                    safeList.Enqueue(i);
    
                });
    
                Parallel.For(maxNum / 2 + 1, maxNum, (i) =>
    
                {
    
                    safeList.Enqueue(i);
    
                });
    
            }
    View Code

    测试结果:

     

    测试结论:

    ConcurrentQueue是线程安全的。在遇到多线程和并行开发时应该使用ConcurrentQueue而不是List.

     

    疑问:为了确保对象线程安全,系统底层实现机制必定是要使用对象加锁和解锁的功能来确保对象安全,那么加锁和解锁是否会对性能造成影响呢?

    我们再来进行一组测试:

    测试条件:

    (1)对100万个数据进行普通的循环运算然后添加到List集合对象中。

    (2)对100万个数据进行并行循环运算然后添加到ConcurrentQueue集合对象中。 

    测试代码:

    static int maxNum = 1000000;
    
            static List<BigInteger> list = new List<BigInteger>();
    
            static ConcurrentQueue<BigInteger> safeList = new ConcurrentQueue<BigInteger>();
    
            static void Main(string[] args)
    
            {
    
                //迭代次数
    
                int iterationNum = 3;
    
                CodeTimer.Initialize();
    
                CodeTimer.Time("普通的循环运算", iterationNum, new Action(NormalCompute));
    
                CodeTimer.Time("并行循环运算_1", iterationNum, new Action(ParallelCompute_1));
    
                CodeTimer.Time("并行循环运算_2", iterationNum, new Action(ParallelCompute_2));
    
                Console.Read();
    
            }
    
            private static void NormalCompute()
    
            {
    
                for (int i = 1; i <= maxNum; i++)
    
                {
    
                    Math.Pow(i, i + 1);
    
                    list.Add(new BigInteger(i));
    
                }
    
            }
    
            private static void ParallelCompute_1()
    
            {
    
                Parallel.For(1, maxNum, (i) =>
    
                    {
    
                        Math.Pow(i, i + 1);
    
                        safeList.Enqueue(new BigInteger(i));
    
                    });
    
            }
    
            private static void ParallelCompute_2()
    
            {
    
                Parallel.For(1, maxNum / 2, (i) =>
    
                {
    
                    Math.Pow(i, i + 1);
    
                    safeList.Enqueue(new BigInteger(i));
    
                });
    
                Parallel.For(maxNum / 2 + 1, maxNum, (i) =>
    
                {
    
                    Math.Pow(i, i + 1);
    
                    safeList.Enqueue(new BigInteger(i));
    
                });
    
            }
    View Code

    测试结果:

     测试结论:

    和我预期的结论是一样的,并行方式所耗费的时间更加的长。线程安全的加锁和解锁对性能的影响还是比较大的。

  • 相关阅读:
    eclipse、idea安装lombok插件
    ContextLoaderListener加载过程
    web.xml 文件中一般包括 servlet, spring, filter, listenr的配置的加载顺序
    springboot
    root cause org.apache.ibatis.ognl.OgnlException: source is null for getProperty(null, "XXX")
    Java MyBatis 插入数据库返回主键
    Mybatis异常There is no getter for property named 'XXX' in 'class com.xxx.xxx.UserAccountDTO
    web项目,ftl文件中的路径引入问题
    mybatis No enum const class org.apache.ibatis.type.JdbcType.Integer
    Restful、Jersey和JAX-RS
  • 原文地址:https://www.cnblogs.com/eric-xiongzw/p/4083427.html
Copyright © 2020-2023  润新知