多重影分身——C#中多线程的使用二(争抢共享资源)
只要服务器承受得了,我们可以开任意个线程同时工作以提高效率,然而
两个线程争抢资源可能导致数据混乱。
例如:
public class MyFood
{
public static int Last { get; set; }
public MyFood()
{
Last = ;
}
public void EatFood()
{
int foods = Last;
Thread.Sleep();
Last = foods - ;
Console.WriteLine(Last);
}
public void EatMuchFood()
{
int foods = Last;
Thread.Sleep();
Last = foods - ;
Console.WriteLine(Last);
}
}
这里定义了个MyFood类,里面有个静态变量Last,存储剩余的食物。然后用构造函数假设初始有500的食物。
两个方法,分别是吃一个食物和吃10个食物,用Thread.Sleep()模拟吃东西需要花费时间。
OK,现在开始吃东西了:
MyFood e = new MyFood ();
Thread th1 = new Thread(new ThreadStart(e.EatFood));
Thread th2 = new Thread(new ThreadStart(e.EatMuchFood));
th1.Start(); //499
th1.Join(); //这里等待第一个线程结束,再往后面走
th2.Start(); //489 Console.ReadKey();
没有任何问题,吃完第一个食物花费1秒钟,剩余499个。然后花费3秒钟吃10个食物,剩余489个。
假如我们不加th1.Join(), 那就是正常情况下的多线程,看看会发生什么:
MyFood e = new MyFood ();
Thread th1 = new Thread(new ThreadStart(e.EatFood));
Thread th2 = new Thread(new ThreadStart(e.EatMuchFood));
th1.Start(); //
th2.Start(); // Console.ReadKey();
可以看到两次输出分别是499和490,这里就有问题了。
EatFood方法,取出剩余Last后休息了1秒,然后再把-1后的数字赋值给Last。恰好,在这1秒期间,我们的th2进来调用EatMuchFood方法了,它也是先取总数,而这个总数却是EatFood方法还没有-1的总数,于是它取到的是500而不是499.
这就是问题发生的原因。
那么在对待总数这种共享资源上,同一时间我们希望只有一个线程来访问(这样才能确保数据正确),其中一个解决方案就是Lock
Lock
public class MyFood
{
private static readonly object lockHelper = new object();
public static int Last { get; set; }
public MyFood()
{
Last = ;
}
public void EatFood()
{
lock (lockHelper)
{
int foods = Last;
Thread.Sleep();
Last = foods - ;
}
Console.WriteLine(Last);
}
public void EatMuchFood()
{
lock (lockHelper)
{
int foods = Last;
Thread.Sleep();
Last = foods - ;
}
Console.WriteLine(Last);
}
}
这里添加了一个静态只读私有变量叫做lockHelper,然后修改了两个吃东西的方法,加了对lockHelper的锁定。
这样每次执行的时候,会判断lockHelper是否被锁定了,如果没有,就进入lock代码块把lockHelper锁定,并且执行其中的代码。出了lock代码块会自动释放锁。
这样就能保证在修改Last的时候,一次只有一个线程了。
本例中直接用lock(this)就行,this代表MyFood e 这个类的实例。
Monitor
Monitor是一个静态类,无法被实例化,有两个常用方法:
Monitor.Enter(object); 锁定传入对象保证只由当前线程操作。
Monitor.Exit(object); 释放被锁定的对象。
lock(obj) 的本质就是调用Monitor。
当我们锁定的是实例时,可能会导致多个实例间互斥不能实现:
public class MyFood
{
private static readonly object lockHelper = new object();
public static int Last { get; set; }
public MyFood()
{
Last = ;
}
public void EatFood()
{
Monitor.Enter(this);int foods = Last;
Thread.Sleep();
Last = foods - ;
Monitor.Exit(this);
Console.WriteLine(Last);
}
} static void Main(string[] args)
{
MyFood e = new MyFood();
MyFood f = new MyFood();
Thread th1 = new Thread(new ThreadStart(e.EatFood));
Thread th2 = new Thread(new ThreadStart(f.EatFood));
th1.Start(); //499
th2.Start(); //499 Console.ReadKey();
}
因为我们锁定的是this,而this指向了不同的实例。
解决方法是锁定this的类型或者静态只读私有变量,即lock(typeof(this)) 或者 lock(lockHelper)
最好不要锁定string。
Monitor.Wait(obj) 和 Monitor.Pulse(obj)
这两个方法都是写在Monitor.Enter(obj)和Monitor.Pulse(obj)中间的。
Monitor.Wait(obj) ,用于在锁定对象之后,暂时释放锁,这样可以让其他线程也能访问。此时,它在等待其他Monitor.Pulse(obj)的通知,一旦收到,就继续往下执行。
Monitor.Pulse(obj), 用于告诉其他线程:我事忙完了,等我Monitor.Exit(obj)了,你们就接着弄吧
例子:
public class MyFood
{
private static readonly object lockHelper = new object();
public static int Last { get; set; }
public MyFood()
{
Last = ;
}
public void EatFood()
{
Monitor.Enter(lockHelper); //锁定lockHelper
int foods = Last;
Thread.Sleep();
Last = foods - ;
Console.WriteLine("第一个线程,剩余:" + Last);
Monitor.Wait(lockHelper); //将该线程暂停,并释放锁允许其他线程访问
//等到了Monitor.Pulse(obj)和Monitor.Exit(obj)的信号,就继续往下执行
Monitor.Exit(lockHelper);
Console.WriteLine("第一个线程,等第二个执行完后剩余:" + Last);
}
public void EatMuchFood()
{
Monitor.Enter(lockHelper); //锁定对象
int foods = Last;
Thread.Sleep();
Last = foods - ;
Monitor.Pulse(lockHelper); //通知其他线程,我忙完了,等我Monitor.Exit(obj)了,你们就继续吧
Monitor.Exit(lockHelper); //释放锁
Console.WriteLine("第二个线程,剩余:" + Last);
}
} class Program
{
static void Main(string[] args)
{
MyFood m = new MyFood();
Thread th1 = new Thread(new ThreadStart(m.EatFood));
Thread th2 = new Thread(new ThreadStart(m.EatMuchFood));
th1.Start();
th2.Start();
Console.ReadKey();
}
}
这里输出结果为:
第一个线程,剩余:499
第二个线程,剩余:489
第一个线程,等第二个执行完后剩余:489
多重影分身——C#中多线程的使用二(争抢共享资源)的更多相关文章
- 多重影分身——C#中多线程的使用一(基础)
首先明确几个概念: 什么是进程? 当一个程序开始运行时,它就是一个进程,进程包括运行中的程序和程序所使用到的内存和系统资源. 而一个进程又是由多个线程所组成的. 一个程序通常只有一个进程(不包括exe ...
- 网络与多线程---OC中多线程方法GCD(二)
小编在前一篇中介绍了多线程实现的五种常用方法.在接下来所介绍的这种方法是最具有魅力的,最具有诱惑的实现多线程的方案---GCD 一.什么是GCD GCD是Grand Central Dispatch的 ...
- iOS中多线程知识总结(二)
1.GCD GCD全称是Grand Central Dispatch,译为"强大的中枢管理器" 1)什么是任务?什么是队列? 任务和队列是GCD的核心. 任务: 执行什么操作 队列 ...
- 多重影分身——C#中多线程的使用三(调用方法和传参)
对Thread: 1.使用ThreadStart static void Main(string[] args) { Thread th1=new Thread(new ThreadStart(Say ...
- Java进阶(四十二)Java中多线程使用匿名内部类的方式进行创建3种方式
Java中多线程使用匿名内部类的方式进行创建3种方式 package cn.edu.ujn.demo; // 匿名内部类的格式: public class ThreadDemo { public st ...
- Java多线程编程(二)对象及变量的并发访问
一.synchronized同步方法 1.方法内的变量为线程安全 “非线程安全”问题存在于“实例变量”中,如果是方法内部的私有变量,则不存在“非线程安全”问题,所得结果也就是“线程安全”的了. 示例: ...
- iOS中多线程知识总结(一)
这一段开发中一直在处理iOS多线程的问题,但是感觉知识太散了,所以就把iOS中多线程的知识点总结了一下. 1.基本概念 1)什么是进程?进程的特性是什么? 进程是指在系统中正在运行的一个应用程序. ...
- Android多线程分析之二:Thread的实现
Android多线程分析之二:Thread的实现 罗朝辉 (http://www.cnblogs.com/kesalin/) CC 许可,转载请注明出处 在前文<Android多线程分析之一 ...
- Java中多线程原理详解
Java是少数的集中支持多线程的语言之一,大多数的语言智能运行单独的一个程序块,无法同时运行不同的多个程序块,Java的多线程机制弥补了这个缺憾,它可以让不同的程序块一起运行,这样可以让程序运行更加顺 ...
随机推荐
- Java中类的创建及类与对象的关系
//import java.util.Scanner; //创建一个类 class Person{ //属性和方法的定义不是必须的 //属性 String name ; int age ; //方法 ...
- xml解析之sax解析原理图和技术介绍
SAX解析工具- Sun公司提供的.内置在jdk中.org.xml.sax.* 核心的API: SAXParser类: 用于读取和解析xml文件对象 parse(File f,DefaultHand ...
- 【一天一道LeetCode】#94. Binary Tree Inorder Traversal
一天一道LeetCode 本系列文章已全部上传至我的github,地址:ZeeCoder's Github 欢迎大家关注我的新浪微博,我的新浪微博 欢迎转载,转载请注明出处 (一)题目 Given a ...
- Dynamics CRM 2013 subgrid刷新后刷新主表单
项目中会遇到这样的需求,会根据subgrid中的数据变化去更新主表单中的某个或多个字段值,一般的做法就是写插件根据subgrid中实体数据的变化在后台更新主表数据库,但后台更新了要反映到前台就得使用刷 ...
- Strategy 设计模式 策略模式 超靠谱原代码讲解
先来假设一种情,我们需要向三种不同的客户做出不同的报价,一般来说要肿么设计呢,是不是马上会想到用IF,没有错,对于这种情况,策略模式是最好的选.大家可以这么理解,如果有情况需要用到大量的IF,那你用策 ...
- 自定义gradle的property
在android studio上运行项目,gradle的配置是必不可少的,但是随着项目的逐渐成长,迎面而来的就是.各种依赖包的添加,数不胜数的签名,渠道包等,整个gradle变得很乱,这样其实我们可以 ...
- Volley请求
1. Volley简介 我们平时在开发Android应用的时候不可避免地都需要用到网络技术,而多数情况下应用程序都会使用HTTP协议来发送和接收网络数据.Android系统中主要提供了两种方式来进行H ...
- 简单说说Android自定义view学习推荐的方式
这几天比较受关注,挺开心的,嘿嘿. 这里给大家总结一下学习自定义view的一些技巧. 以后写自定义view可能不会写博客了,但是可以开源的我会把源码丢到github上我的地址:https://git ...
- (七)大图展示Demo引出的UIScrollView的使用
UIScrollView是一个能够滚动的视图控件,可以通过滚动查看所有内容. 用途: 1.一张大图屏幕放不下,可以用各个方向的手势来看大图的各个部分. 2.手机的设置页面有很多的选项,需要上下滚动来查 ...
- Swift基础之UITableView(之前写的知识点都是最新的2.2版本样式,欢迎大家参考,可以相互交流)
//这里只是列举了经常使用的UITableView的属性和方法,其他的都可以类似使用,注意用法即可 //设置全局变量UITableView var myTableView = UITa ...