目录:

一、线程同步概述

二、线程同步的使用

三 、总结

一、线程同步概述

前面的文章都是讲创建多线程来实现让我们能够更好的响应应用程序,然而当我们创建了多个线程时,就存在多个线程同时访问一个共享的资源的情况,在这种情况下,就需要我们用到线程同步,线程同步可以防止数据(共享资源)的损坏。

然而我们在设计应用程序还是要尽量避免使用线程同步, 因为线程同步会产生一些问题:

1. 它的使用比较繁琐。因为我们要用额外的代码把多个线程同时访问的数据包围起来,并获取和释放一个线程同步锁,如果我们在一个代码块忘记获取锁,就有可能造成数据损坏。

2. 使用线程同步会影响性能,获取和释放一个锁肯定是需要时间的吧,因为我们在决定哪个线程先获取锁时候, CPU必须进行协调,进行这些额外的工作就会对性能造成影响

3. 因为线程同步一次只允许一个线程访问资源,这样就会阻塞线程,阻塞线程会造成更多的线程被创建,这样CPU就有可能要调度更多的线程,同样也对性能造成了影响。

所以在实际的设计中还是要尽量避免使用线程同步,因此我们要避免使用一些共享数据,例如静态字段。

二、线程同步的使用

2.1 对于使用锁性能的影响

上面已经说过使用锁将会对性能产生影响, 下面通过比较使用锁和不使用锁时消耗的时间来说明这点

using System;
using System.Diagnostics;
using System.Threading; namespace InterlockedSample
{
// 比较使用锁和不使用锁锁消耗的时间
// 通过时间来说明使用锁性能的影响
class Program
{
static void Main(string[] args)
{
int x = 0;
// 迭代次数为500万
const int iterationNumber = 5000000;
// 不采用锁的情况
// StartNew方法 对新的 Stopwatch 实例进行初始化,将运行时间属性设置为零,然后开始测量运行时间。
Stopwatch sw = Stopwatch.StartNew();
for (int i = 0; i < iterationNumber; i++)
{
x++;
} Console.WriteLine("Use the all time is :{0} ms", sw.ElapsedMilliseconds); sw.Restart();
// 使用锁的情况
for (int i = 0; i < iterationNumber; i++)
{
Interlocked.Increment(ref x);
} Console.WriteLine("Use the all time is :{0} ms", sw.ElapsedMilliseconds);
Console.Read();
}
}
}

运行结果(这是在我电脑上运行的结果)从结果中可以看出加了锁的运行速度慢了好多(慢了11倍 197/18 ):

2.2 Interlocked实现线程同步

Interlocked类提供了为多个线程共享的变量提供原子操作,当我们在多线程中对一个整数进行递增操作时,就需要实现线程同步。

因为增加变量操作(++运算符)不是一个原子操作,需要执行下列步骤:

1)将实例变量中的值加载到寄存器中。

2)增加或减少该值。

3)在实例变量中存储该值。

如果不使用 Interlocked.Increment方法,线程可能会在执行完前两个步骤后被抢先。然后由另一个线程执行所有三个步骤,此时第一个线程还没有把变量的值存储到实例变量中去,而另一个线程就可以把实例变量加载到寄存器里面读取了(此时加载的值并没有改变),所以会导致出现的结果不是我们预期的,相信这样的解释可以帮助大家更好的理解Interlocked.Increment方法和 原子性操作,

下面通过一段代码来演示下加锁和不加锁的区别(开始讲过加锁会对性能产生影响,这里将介绍加锁来解决线程同步的问题,得到我们预期的结果):

不加锁的情况:

class Program
{
static void Main(string[] args)
{ for (int i = 0; i < 10; i++)
{
Thread testthread = new Thread(Add);
testthread.Start();
} Console.Read();
} // 共享资源
public static int number = 1; public static void Add()
{
Thread.Sleep(1000);
Console.WriteLine("the current value of number is:{0}", ++number);
}
}

运行结果(不同电脑上可能运行的结果和我的不一样,但是都是得到不是预期的结果的):

为了解决这样的问题,我们可以通过使用 Interlocked.Increment方法来实现原子的自增操作。

代码很简单,只需要把++number改成Interlocked.Increment(ref number)就可以得到我们预期的结果了,在这里代码和运行结果就不贴了。

总之Interlocked类中的方法都是执行一次原子读取以及写入的操作的。

2.3 Monitor实现线程同步

对于上面那个情况也可以通过Monitor.Enter和Monitor.Exit方法来实现线程同步。C#中通过lock关键字来提供简化的语法(lock可以理解为Monitor.Enter和Monitor.Exit方法的语法糖),代码也很简单:

using System;
using System.Threading; namespace MonitorSample
{
class Program
{
static void Main(string[] args)
{
for (int i = 0; i < 10; i++)
{
Thread testthread = new Thread(Add);
testthread.Start();
} Console.Read();
} // 共享资源
public static int number = 1; public static void Add()
{
Thread.Sleep(1000);
//获得排他锁
Monitor.Enter(number); Console.WriteLine("the current value of number is:{0}", number++); // 释放指定对象上的排他锁。
Monitor.Exit(number);
}
}
}

运行结果当然是我们所期望的:

在 Monitor类中还有其他几个方法在这里也介绍,只是让大家引起注意下,一个Wait方法,很明显Wait方法的作用是:释放某个对象上的锁以便允许其他线程锁定和访问这个对象。第二个就是TryEnter方法,这个方法与Enter方法主要的区别在于是否阻塞当前线程,当一个对象通过Enter方法获取锁,而没有执行Exit方法释放锁,当另一个线程想通过Enter获得锁时,此时该线程将会阻塞,直到另一个线程释放锁为止,而TryEnter不会阻塞线程。具体代码就不不写出来了。

2.4 ReaderWriterLock实现线程同步

如果我们需要对一个共享资源执行多次读取时,然而用前面所讲的类实现的同步锁都只允许一个线程允许,所有线程将阻塞,但是这种情况下肯本没必要堵塞其他线程, 应该让它们并发的执行,因为我们此时只是进行读取操作,此时通过ReaderWriterLock类可以很好的实现读取并行。

演示代码为:

using System;
using System.Collections.Generic;
using System.Threading; namespace ReaderWriterLockSample
{
class Program
{
public static List<int> lists = new List<int>(); // 创建一个对象
public static ReaderWriterLock readerwritelock = new ReaderWriterLock();
static void Main(string[] args)
{
//创建一个线程读取数据
Thread t1 = new Thread(Write);
t1.Start();
// 创建10个线程读取数据
for (int i = 0; i < 10; i++)
{
Thread t = new Thread(Read);
t.Start();
} Console.Read(); } // 写入方法
public static void Write()
{
// 获取写入锁,以10毫秒为超时。
readerwritelock.AcquireWriterLock(10);
Random ran = new Random();
int count = ran.Next(1, 10);
lists.Add(count);
Console.WriteLine("Write the data is:" + count);
// 释放写入锁
readerwritelock.ReleaseWriterLock();
} // 读取方法
public static void Read()
{
// 获取读取锁
readerwritelock.AcquireReaderLock(10); foreach (int li in lists)
{
// 输出读取的数据
Console.WriteLine(li);
} // 释放读取锁
readerwritelock.ReleaseReaderLock();
}
}
}

运行结果:

三、总结

本文中主要介绍如何实现多线程同步的问题, 通过线程同步可以防止共享数据的损坏,但是由于获取锁的过程会有性能损失,所以在设计应用过程中尽量减少线程同步的使用。本来还要介绍互斥(Mutex), 信号量(Semaphore), 事件构造的, 由于篇幅的原因怕影响大家的阅读,所以这剩下的内容放在后面介绍的。

http://www.cnblogs.com/zhili/archive/2012/07/21/ThreadsSynchronous.html

[C# 线程处理系列]专题四:线程同步的更多相关文章

  1. 线程池系列一:线程池作用及Executors方法讲解

    线程池的作用: 线程池作用就是限制系统中执行线程的数量.     根据系统的环境情况,可以自动或手动设置线程数量,达到运行的最佳效果:少了浪费了系统资源,多了造成系统拥挤效率不高.用线程池控制线程数量 ...

  2. [C# 基础知识系列]专题四:事件揭秘

    转自http://www.cnblogs.com/zhili/archive/2012/10/27/Event.html 引言: 前面几个专题对委托进行了详细的介绍的,然后我们在编写代码过程中经常会听 ...

  3. [C# 基础知识系列]专题四:事件揭秘 (转载)

    引言: 前面几个专题对委托进行了详细的介绍的,然后我们在编写代码过程中经常会听到“事件”这个概念的,尤其是写UI的时候,当我们点击一个按钮后VS就会自动帮我们生成一些后台的代码,然后我们就只需要在Cl ...

  4. [.NET领域驱动设计实战系列]专题四:前期准备之工作单元模式(Unit Of Work)

    一.前言 在前一专题中介绍了规约模式的实现,然后在仓储实现中,经常会涉及工作单元模式的实现.然而,在我的网上书店案例中也将引入工作单元模式,所以本专题将详细介绍下该模式,为后面案例的实现做一个铺垫. ...

  5. [C# 网络编程系列]专题四:自定义Web浏览器

    转自:http://www.cnblogs.com/zhili/archive/2012/08/24/WebBrowser.html 前言: 前一个专题介绍了自定义的Web服务器,然而向Web服务器发 ...

  6. [.NET领域驱动设计实战系列]专题十一:.NET 领域驱动设计实战系列总结

    一.引用 其实在去年本人已经看过很多关于领域驱动设计的书籍了,包括Microsoft .NET企业级应用框架设计.领域驱动设计C# 2008实现.领域驱动设计:软件核心复杂性应对之道.实现领域驱动设计 ...

  7. [ 高并发]Java高并发编程系列第二篇--线程同步

    高并发,听起来高大上的一个词汇,在身处于互联网潮的社会大趋势下,高并发赋予了更多的传奇色彩.首先,我们可以看到很多招聘中,会提到有高并发项目者优先.高并发,意味着,你的前雇主,有很大的业务层面的需求, ...

  8. Java Thread系列(四)线程通信

    Java Thread系列(四)线程通信 一.传统通信 public static void main(String[] args) { //volatile实现两个线程间数据可见性 private ...

  9. Android异步处理系列文章四篇之一使用Thread+Handler实现非UI线程更新UI界面

    目录: Android异步处理一:使用Thread+Handler实现非UI线程更新UI界面Android异步处理二:使用AsyncTask异步更新UI界面Android异步处理三:Handler+L ...

随机推荐

  1. Hive的JDBC访问

    实现hive查询源码: String driverName = "org.apache.hive.jdbc.HiveDriver"; try { Class.forName(dri ...

  2. virtualvm一次插件安装想到的

    在麒麟操作系统visualvm安装插件失败,因为使用的内网,所以在官网下载了插件到本地:因为本地安装的jdk1.6,为了享受jdk1.8,在visualvm文件中增加了对于jdk1.8的引用: exp ...

  3. GWT嵌入纯HTML页面

    众所周知,gwt页面是java代码所写,不存在html页面直接作用于gwt面板中.不过gwt也倒是提供了一些可用的功能,比如frame,这个是UI中的一个,内部可以设置URL,但是经过我测试后发现,这 ...

  4. 蓝桥杯 算法训练 ALGO-145 4-1打印下述图形

     算法训练 4-1打印下述图形   时间限制:1.0s   内存限制:256.0MB 问题描述 使用循环结构打印下述图形,打印行数n由用户输入.打印空格时使用"%s"格式,向pri ...

  5. Mongodb 3.6 副本集测试及添加删除节点等操作

    下载tar包并安装curl -O https://fastdl.mongodb.org/linux/mongodb-linux-x86_64-rhel70-3.6.8.tgz [root@mysqlt ...

  6. Intellij IDEA 发布后的项目在哪里

    Intellij IDEA 中使用 tomcat 并发布项目后,项目并没有出现在在 webapps 文件夹中,如果没有手动修改过部署目录的话,idea的真实部署目录为 File---->Proj ...

  7. Spring学习八

    1: Tomcat容器四个等级? Container, Engine,  Servlet容器, Context 真正管理Servlet的容器是Context容器:一个context对应一个web工程. ...

  8. JDBC---bai

    import java.sql.Connection; import java.sql.DriverManager; import java.sql.PreparedStatement; import ...

  9. 2015.7.24 CAD库中列举五字代码点所属航路及终端区图,左连接的累加

    select decode(fb.tupr,null,'仅航路',decode(fc.aw,null,'仅终端区','航路及终端区')) 范围,pt 五字代码点,fb.tupr 终端区图及程序,fc. ...

  10. VS2008 C++ 项目怎样添加“依赖”、“库目录”和“包含目录”

    随笔 - 79, 文章 - 0, 评论 - 7, 引用 - 0 1. 添加编译所需要(依赖)的 lib 文件 [解决方案资源管理器]“项目->属性->配置属性->连接器->输入 ...