C#线程--5.0之前时代(二)、线程的同步

线程同步

说明:接上一篇,注意分享线程同步的必要性和线程同步的方法。

一、什么是线程同步:

在同一时间只允许一个线程访问资源的情况称为线程同步。

二、为什么需要线程同步:

  • 避免竞争条件;
  • 确保线程安全;(如果两个线程同时访问一个资源并对那个资源做修改,就不安全了)

现在的计算机变得越来越多核,每一个CPU可以独立工作,但是对于内存和外部资源、数据库的访问却可能因为不同线程的访问使数据产生异常,常见的例子就是银行的转账的例子不再赘述。

三、线程同步的方法:

  • 同步代码中重要的部分;
  • 使对象不可改变;
  • 使用线程安全包装器;

    注意:局部变量、方法参数和返回值是放在堆栈中的,本身是线程安全的。

四、线程不安全的演示:

背景:在数据库的user_blance表插入两条数据,两人的balance值都为3000.00,整个user_balance表的balance总值为6000.00

        static string connectionStr = "Server=127.0.0.1;Port=3306;Stmt=;Database=exe_dev; User=root;Password=123456";
        public static void UnSafeThread() {
            Thread ThreadOne = new Thread(new ThreadStart(DrawMoney));
            ThreadOne.Name = "A001";

            Thread ThreadTwo = new Thread(new ThreadStart(DrawMoney));
            ThreadTwo.Name = "A002";

            ThreadOne.Start();
            ThreadTwo.Start();
            string sumMonney = "select sum(balance) from user_balance";

            int count = 0;
            while (count < 200)
            {
                using (MySqlConnection conn = MySqlConnectionHelper.OpenConnection(connectionStr))
                {
                    var balance = conn.ExecuteScalar(sumMonney);
                    Console.WriteLine("sum money:" + balance);
                }

                count++;
            }
        }

        private static void DoDrawMoney()
        {
            Random random = new Random();
            int money = random.Next(100);

            string userId = Thread.CurrentThread.Name;
            string selectSql = "select balance from user_balance where [email protected]";
            string updateSql = "update user_balance set [email protected][email protected] where [email protected]";
            string updateSql2 = "update user_balance set [email protected]@Money where user_id<>@UserId";
            using (MySqlConnection conn= MySqlConnectionHelper.OpenConnection(connectionStr))
            {
                var balance = conn.ExecuteScalar(selectSql, new { UserId = userId });
                if (balance != null)
                {
                    conn.Execute(updateSql, new { Money = money, Balance=balance, UserId = userId });
                    conn.Execute(updateSql2, new { Money = money, Balance = balance, UserId = userId });
                }
            }
        }

        private static void DrawMoney() {
            for (int i = 0; i < 100; i++)
            {
                DoDrawMoney();
            }
        }

运行结果:

...

sum money:6000.00
sum money:6000.00
sum money:6025.00
sum money:5975.00
sum money:6056.00
sum money:5813.00
sum money:5943.00
sum money:5681.00
sum money:5455.00
sum money:5375.00

...

程序中有三条线程在跑:两条支线程,一条主线程,主线程负责统计钱的总数,两条支线程模拟两个人赚钱,赚过来赚过去,哈哈哈,依据查询成果可以看到,钱的总数原本是6000.00,但是之后开始减少。当然上面的异常也可以通过加事务解决,或者改变sql的实现方式balance=balance+money,不过这个不是我们讨论的重点,不展开。

持续更新中...

原文地址:https://www.cnblogs.com/heisehenbai/p/9960978.html

时间: 2024-08-30 03:26:24

C#线程--5.0之前时代(二)、线程的同步的相关文章

.NET组件程序设计之线程、并发管理(二)

.Net组件程序设计之线程.并发管理(二) 2.同步线程 手动同步 监视器 互斥 可等待事件 同步线程 所有的.NET组件都支持在多线程的环境中运行,可以被多个线程并发访问,如果没有线程同步,这样的后果是当多个线程同时访问 对象状态时,对象的状态可能被破坏,造成不一致性..NET提供了两种方法来避免这样的问题,使得我们设计的组件更加健壮. 第一种是自动同步,让你使用一个属性来修饰组件,这样就可以把组件交给.NET了,同步的事情也就交给了.NET. 第二种是手动同步,这是让你使用.NET提供的同步

线程的生命周期以及控制线程

一.线程的生命周期 线程状态转换图: 1.新建状态 用new关键字和Thread类或其子类建立一个线程对象后,该线程对象就处于新生状态.处于新生状态的线程有自己的内存空间,通过调用start方法进入就绪状态(runnable). 注意:不能对已经启动的线程再次调用start()方法,否则会出现java.lang.IllegalThreadStateException异常. 2.就绪状态 处于就绪状态的线程已经具备了运行条件,但还没有分配到CPU,处于线程就绪队列(尽管是采用队列形式,事实上,把它

Java基础系列之(二) - 线程

一.线程的实现方式 1.继承Thread 2.实现Runnable接口 二.线程的状态 1.New(新生线程) 当你new一个Thread,newThread(r),这时处于线程的新生状态,此时程序还没有真正的运行. 2.Runnable(可运行的) 当启动start()方法时,此时线程处于可运行状态,不一定运行之中,这取决与线程是否得到CPU的运行时间片.事实上,一个线程并不是一直处于运行状态,偶尔需要被中断,让其他线程有运行的机会. 3.Blocked(被阻塞) 当发生以下情况被阻塞 -线程

C# 多线程(二) 线程同步基础(上)

本系列的第一篇简单介绍了线程的概念以及对线程的一些简单的操作,从这一篇开始讲解线程同步,线程同步是多线程技术的难点.线程同步基础由以下几个部分内容组成 1.同步要领(Synchronization Essentials) 2.锁(Locking) 3.线程安全(Thread Safety) 4.事件等待句柄(Signaling with Event Wait Handles) 5.同步上下文(Synchronization Contexts) 同步要领(Synchronization Essen

Java线程池使用和分析(二) - execute()原理

相关文章目录: Java线程池使用和分析(一) Java线程池使用和分析(二) - execute()原理 execute()是 java.util.concurrent.Executor接口中唯一的方法,JDK注释中的描述是“在未来的某一时刻执行命令command”,即向线程池中提交任务,在未来某个时刻执行,提交的任务必须实现Runnable接口,该提交方式不能获取返回值.下面是对execute()方法内部原理的分析,分析前先简单介绍线程池有哪些状态,在一系列执行过程中涉及线程池状态相关的判断

爪哇国新游记之十二----线程创建的两种形式

public class Thread1 extends Thread{ public void run(){ int i=0; while(i<10){ i++; System.out.println(i); } } public static void main(String[] args){ Thread1 t=new Thread1(); t.start(); } } public class Thread2 implements Runnable{ @Override public v

JAVA线程池 之 Executors (二) 原理分析

一.线程池状态 private final AtomicInteger ctl = new AtomicInteger(ctlOf(RUNNING, 0)); private static final int COUNT_BITS = Integer.SIZE - 3; private static final int CAPACITY = (1 << COUNT_BITS) - 1; // runState is stored in the high-order bits private s

Java线程池详解(二)

一.前言 在总结了线程池的一些原理及实现细节之后,产出了一篇文章:Java线程池详解(一),后面的(一)是在本文出现之后加上的,而本文就成了(二).因为在写完第一篇关于java线程池的文章之后,越发觉得还有太多内容需要补充,每次都是修修补补,总觉得还缺点什么.在第一篇中,我着重描述了java线程池的原理以及它的实现,主要的点在于它是如何工作的.而本文的内容将更为上层,重点在于如何应用java线程池,算是对第一篇文章的一点补充,这样对于java线程池的学习和总结稍微完整一些. 使用过java线程池

关于Java线程池详解(二)

一.为什么要用线程池? 1.减少资源的开销 : 2.减少了每次创建线程.销毁线程的开销: 3.提高响应速度 ,每次请求到来时,由于线程的创建已经完成,故可以直接执行任务,因此提高了响应速度. 提高线程的可管理性 ,线程是一种稀缺资源,若不加以限制,不仅会占用大量资源,而且会影响系统的稳定性. 因此,线程池可以对线程的创建与停止.线程数量等等因素加以控制,使得线程在一种可控的范围内运行,不仅能保证系统稳定运行,而且方便性能调优. 二.Executor框架中的所有类可以分成三类: 1.任务: 任务有