本笔记摘抄自:https://www.cnblogs.com/zhili/archive/2012/07/21/ThreadsSynchronous.html,记录一下学习过程以备后续查用。

    一、线程同步概述

创建多线程来实现让我们能够更好地响应应用程序,然而当我们创建了多个线程时,就存在多个线程同时访问一个共享资源的情况。此时,我们就需要用到线程同步。线程同

步可以防止数据(共享资源)的损坏。

一般来说,设计应用程序应尽量避免使用线程同步, 因为线程同步会产生一些问题:

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

1.2、使用线程同步会影响性能。

1.2.1、获取和释放一个锁是需要时间的,我们在决定哪个线程先获取锁的时候,CPU要进行协调,这些额外的工作就会对性能造成影响。

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

    二、线程同步使用

    2.1 使用锁对性能的影响

1.2.1描述过使用锁会对性能产生影响,下面通过比较使用锁和不使用锁消耗的时间来说明这点:

    class Program
{
static void Main(string[] args)
{
#region 线程同步:使用与不使用锁的耗时对比
int x = ;
//迭代500万次
const int iterationNumber = ; //不使用锁
Stopwatch sw = Stopwatch.StartNew();
for (int i = ; i < iterationNumber; i++)
{
x++;
}
Console.WriteLine("Total time consuming is:{0}ms.", sw.ElapsedMilliseconds); sw.Restart();
//使用锁
for (int i = ; i < iterationNumber; i++)
{
Interlocked.Increment(ref x);
} Console.WriteLine("Total time consuming is:{0}ms.", sw.ElapsedMilliseconds);
Console.Read();
#endregion
}
}

运行结果如下:

    2.2 Interlocked实现线程同步

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

下面代码演示加锁与不加锁的区别:

不加锁:

    class Program
{
//共享资源
public static int number = ; static void Main(string[] args)
{
#region 线程同步:使用Interlocked实现线程同步
//不加锁
for (int i = ; i < ; i++)
{
Thread thread = new Thread(Add);
thread.Start();
}
Console.Read();
#endregion
} /// <summary>
/// 递增不加锁
/// </summary>
public static void Add()
{
Thread.Sleep();
Console.WriteLine("The current value of number is:{0}", ++number);
}
}

运行结果如下:

结果与预期可能不太一样。为了解决这样的问题,我们可以通过使用 Interlocked.Increment方法来实现自增操作。

实现原理:类似银行叫号,当有空号且号码是自己的,才能去办理相关的业务,否则继续等待。

加锁:

    class Program
{
//共享资源
public static int number = ;
public static long signal = ; static void Main(string[] args)
{
#region 线程同步:使用Interlocked实现线程同步
//加锁
for (int i = ; i < ; i++)
{
Thread thread = new Thread(new ParameterizedThreadStart(AddWithInterlocked));
thread.Start(i);
}
Console.Read();
#endregion
} /// <summary>
/// 递增加Interlocked锁
/// </summary>
public static void AddWithInterlocked(object parameter)
{
while (Interlocked.Read(ref signal) != || (int)parameter != number)
{
Thread.Sleep();
} Interlocked.Increment(ref signal);
Console.WriteLine("The current value of number is:{0}", ++number);
Interlocked.Decrement(ref signal);
}
}

运行结果如下:

2.3 Monitor实现线程同步

对于上面那个情况,也可以通过Monitor.Enter和Monitor.Exit方法来实现线程同步。

C#中通过lock关键字来提供简化的语法(lock可以理解为Monitor.Enter和Monitor.Exit方法的语法糖)。

    class Program
{
//共享资源
public static int number = ;
private static readonly object addLock = new object(); static void Main(string[] args)
{
#region 线程同步:使用Monitor实现线程同步
//非语法糖
for (int i = ; i < ; i++)
{
Thread thread = new Thread(AddWithMonitor);
thread.Start();
}
Console.Read();
//语法糖
//for (int i = 0; i < 10; i++)
//{
// Thread thread = new Thread(AddWithLock);
// thread.Start();
//}
//Console.Read();
#endregion
} /// <summary>
/// 递增加Monitor锁
/// </summary>
public static void AddWithMonitor()
{
Thread.Sleep();
Monitor.Enter(addLock);
Console.WriteLine("The current value of number is:{0}", ++number);
Monitor.Exit(addLock);
} /// <summary>
/// 递增加Lock锁
/// </summary>
public static void AddWithLock()
{
Thread.Sleep();
lock (addLock)
{
Console.WriteLine("The current value of number is:{0}", ++number);
}
}
}

运行结果如下:

接上面的addLock锁(以下描述为obj锁),顺便学习一下Monitor类的原理:

Monitor在锁对象obj上会维持两个线程队列R和W以及一个引用T :

(1)T是对当前获得了obj锁的线程的引用

(2) R为就绪队列。

 R队列上的线程,是已经准备好了去竞争获取obj锁的线程。

线程可通过调用Monitor.Enter(obj)或Monitor.TryEnter(obj)而直接进入R队列,可通过调用Monitor.Exit(obj)或Monitor.Wait(obj)释放其所获得的obj锁。

当obj锁被某个线程释放后,这个队列上的线程就会去竞争obj锁,而获得obj锁的线程将被T引用。

(3) W为等待队列。

    W队列上的线程,是不会被OS直接调度执行的线程。也就是说,等待队列上的线程不能去获得obj锁。

线程可通过调用Monitor.Wait(obj)而直接进入W队列,可通过调用Monitor.Pulse(obj)或Monitor.PulseAll(obj)将W队列中的第一个等待线程或所有等待线程移至R队列

这时被移至R队列的这些线程就有机会被OS直接调度执行,也就是可以去竞争obj锁。

(4)Monitor的成员方法。

    Monitor.Enter(obj)/Monitor.TryEnter(obj) :线程会进入R队列以等待获取obj锁

    Monitor.Exit(obj) :线程释放obj锁(只有获取了obj锁的线程才能执行Monitor.Exit(obj))

    Monitor.Wait(obj): 线程释放当前获得的obj锁,然后进入W队列并阻塞。

    Monitor.Pulse(obj) :将W队列中的第一个等待线程移至R队列中以使第一个线程有机会获取obj锁。

    Monitor.PulseAll(obj):将W队列中的所有等待线程移至R队列以使得这些线程有机会获得obj锁。

下面代码演示Monitor.Wait及Monitor.Pulse的使用:

    class Program
{
//共享资源
private static readonly object addLock = new object(); static void Main(string[] args)
{
#region 线程同步:Monitor.Wait与Monitor.Pulse的使用
for (int i = ; i < ; i++)
{
Thread thread = new Thread(MonitorWaitAndPulse);
thread.Start();
}
Console.Read();
#endregion
} /// <summary>
/// Monitor中的Wait与Pulse方法
/// </summary>
public static void MonitorWaitAndPulse()
{
//进入就绪队列等待获取锁资源
Monitor.Enter(addLock);
//进来打声招呼
Console.WriteLine("{0}:我来了,临时要出去办一下事。", Thread.CurrentThread.ManagedThreadId);
//唤醒等待队列中的第一个线程进入就绪队列
Monitor.Pulse(addLock);
//暂时释放锁资源进入等待队列
Monitor.Wait(addLock);
//出去办事
Thread.Sleep();
//回来打声招呼
Console.WriteLine("{0}:我回来了。", Thread.CurrentThread.ManagedThreadId);
//释放锁资源
Monitor.Exit(addLock);
}
}

运行结果如下:

    2.4 ReaderWriterLock实现线程同步

如果我们需要对一个共享资源执行多次读取时,用前面所讲的类实现的同步锁都仅允许一个线程进行访问,而其它线程将被阻塞。由于只是进行读取操作,其实是没有必要

堵塞其他的线程, 应该让它们并发的执行。

此时,可通过ReaderWriterLock类来实现并行读取。

    class Program
{
//创建对象
public static List<int> lists = new List<int>();
public static ReaderWriterLock readerWriteLock = new ReaderWriterLock(); static void Main(string[] args)
{
#region 线程同步:使用ReaderWriterLock实现线程同步
//创建一个线程读取数据
Thread threadWrite = new Thread(Write);
threadWrite.Start();
//创建10个线程读取数据
for (int i = ; i < ; i++)
{
Thread threadRead = new Thread(Read);
threadRead.Start();
} Console.Read();
#endregion
} /// <summary>
/// 写入方法
/// </summary>
public static void Write()
{
//获取写入锁,以10毫秒为超时。
readerWriteLock.AcquireWriterLock();
Random ran = new Random();
int count = ran.Next(, );
lists.Add(count);
Console.WriteLine("Write the data is:" + count);
//释放写入锁
readerWriteLock.ReleaseWriterLock();
} /// <summary>
/// 读取方法
/// </summary>
public static void Read()
{
Thread.Sleep();
//获取读取锁
readerWriteLock.AcquireReaderLock(); foreach (int list in lists)
{
//输出读取的数据
Console.WriteLine(list);
} // 释放读取锁
readerWriteLock.ReleaseReaderLock();
}
}

运行结果如下:

C#线程学习笔记四:线程同步的更多相关文章

  1. JUC源码学习笔记5——线程池,FutureTask,Executor框架源码解析

    JUC源码学习笔记5--线程池,FutureTask,Executor框架源码解析 源码基于JDK8 参考了美团技术博客 https://tech.meituan.com/2020/04/02/jav ...

  2. 操作系统学习笔记----进程/线程模型----Coursera课程笔记

    操作系统学习笔记----进程/线程模型----Coursera课程笔记 进程/线程模型 0. 概述 0.1 进程模型 多道程序设计 进程的概念.进程控制块 进程状态及转换.进程队列 进程控制----进 ...

  3. C#线程学习笔记九:async & await入门二

    一.异步方法返回类型 只能返回3种类型(void.Task和Task<T>). 1.1.void返回类型:调用方法执行异步方法,但又不需要做进一步的交互. class Program { ...

  4. java学习笔记15--多线程编程基础2

    本文地址:http://www.cnblogs.com/archimedes/p/java-study-note15.html,转载请注明源地址. 线程的生命周期 1.线程的生命周期 线程从产生到消亡 ...

  5. Linux进程线程学习笔记:运行新程序

    Linux进程线程学习笔记:运行新程序                                         周银辉 在上一篇中我们说到,当启动一个新进程以后,新进程会复制父进程的大部份上下 ...

  6. muduo网络库学习笔记(四) 通过eventfd实现的事件通知机制

    目录 muduo网络库学习笔记(四) 通过eventfd实现的事件通知机制 eventfd的使用 eventfd系统函数 使用示例 EventLoop对eventfd的封装 工作时序 runInLoo ...

  7. {Python之线程} 一 背景知识 二 线程与进程的关系 三 线程的特点 四 线程的实际应用场景 五 内存中的线程 六 用户级线程和内核级线程(了解) 七 python与线程 八 Threading模块 九 锁 十 信号量 十一 事件Event 十二 条件Condition(了解) 十三 定时器

    Python之线程 线程 本节目录 一 背景知识 二 线程与进程的关系 三 线程的特点 四 线程的实际应用场景 五 内存中的线程 六 用户级线程和内核级线程(了解) 七 python与线程 八 Thr ...

  8. ZooKeeper学习笔记四:使用ZooKeeper实现一个简单的分布式锁

    作者:Grey 原文地址: ZooKeeper学习笔记四:使用ZooKeeper实现一个简单的分布式锁 前置知识 完成ZooKeeper集群搭建以及熟悉ZooKeeperAPI基本使用 需求 当多个进 ...

  9. Java IO学习笔记四:Socket基础

    作者:Grey 原文地址:Java IO学习笔记四:Socket基础 准备两个Linux实例(安装好jdk1.8),我准备的两个实例的ip地址分别为: io1实例:192.168.205.138 io ...

随机推荐

  1. 常见 MIME 类型列表(转)

    本文转自:https://developer.mozilla.org/zh-CN/docs/Web/HTTP/Basics_of_HTTP/MIME_types/Complete_list_of_MI ...

  2. Solr搜索引擎【索引提交、事务日志、原子更新】

    一.索引提交 当一个文档被添加到Solr中,但没有提交给索引之前,这个文档是无法被搜索的.换句话说,从查询的角度看,文档直到提交之后才是可见的.Solr有两种类型的提交:软提交和正常提交[也称硬提交] ...

  3. App自动化测试-1.App自动化介绍和环境搭建

    App自动化测试-1.App自动化介绍和环境搭建 *:first-child { margin-top: 0 !important; } body>*:last-child { margin-b ...

  4. python面试题(实时更新)

    1.以下代码输出为: list1 = {':2} list2 = list1 list1['] = 5 sum = list1['] print(sum) 解析:10 b = a: 赋值引用,a 和 ...

  5. 【Android - 问题解决】之Android Studio在Refreshing Gradle Project卡住的问题

    今天把Android Studio从2.2版本升级到了2.3版本,但是再打开以前项目的时候,就卡在 “Refreshing XXX Gradle Project” 这里不动了. 网上搜了一些解决方案, ...

  6. php 精度计算问题

    PHP var_dump(intval(0.58 * 100)); 正确结果是 57,而不是 58 浮点运算惹的祸 其实这些结果都并非语言的 bug,但和语言的实现原理有关, js 所有数字统一为 N ...

  7. HTML 表格 各标签使用的标准顺序(心得)

    <table>   <caption>标题</caption>     <colground>     <col>     <col& ...

  8. ASI的其他使用方法

    ASI 除了设置代理监听以外还可以设置block进行监听 如果同时设置block和实现了代理方法 请求过程中 block和代理方法都会调用 一般 代理方法 优先block方法调用 第3种方式调用

  9. Docker Compose集成式应用组合及service编排

    Compose简介 Compose项目是Docker官方的开源项目,负责实现对Docker容器集群的快速编排.其代码目前在 https://github.com/docker/compose 开源.C ...

  10. POJ 2186 Popular cows(SCC 缩点)

    Every cow's dream is to become the most popular cow in the herd. In a herd of N (1 <= N <= 10, ...