本笔记摘抄自: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. echarts对柱状图进行标注,以及取消hover时的阴影

    option = { color: ['#3398DB'], tooltip : { trigger: 'axis', axisPointer : { // 坐标轴指示器,坐标轴触发有效 type : ...

  2. C#学习笔记05--枚举/结构体

    一.枚举   当变量的取值范围是固定的几个时, 就可以使用枚举类型, 这样会更加简洁方便   1.1.定义: 访问修饰符 enum 枚举类型名 { 成员1, 成员2, 成员3, ... } publi ...

  3. OSI-传输层

    OSI-传输层 端口号(2字节 SYN(1bit) ACK(1bit) 会话多路复用(为什么一个IP地址可以做很多事情?) 源端口地址可以不同 五元组(世界上没有相同的2个五元组) 源IP地址-目的I ...

  4. C++ 关键字之override

    非原创,转载自stackoverflow 确切的说override并非一个keyword The override keyword serves two purposes: It shows the ...

  5. 一文读懂Python web框架和web服务器之间的关系

    我们都知道 Python 作为一门强大的语言,能够适应快速原型和较大项目的制作,因此被广泛用于 web 应用程序的开发中. 在面试的过程中,大家或多或少都被问到过这样一个问题:一个请求从浏览器发出到数 ...

  6. zabbix配置

    一:安装zabbix服务端 1.部署准备 命令:iptables -F     #关闭防火墙命令:systemctl stop firewalld    #关闭防火墙 设置解析,自建yum源 命令:c ...

  7. vue反向代理(解决跨域)

    1,vue中有提供反向代理的接口,就是config/index.js中的proxyTable,我的脚手架版本是2.9.6,proxyTable配置初始为空,如下图. 2,将proxyTable配置如下 ...

  8. go基础之不定参函数

    指定类型参数 任意类型参数的变参 go语言同其他编程一样也提供了对变参函数的支持.本文简单讲解一下go中变参函数的使用方法. 指定类型参数 不定参数是指函数传入参数的个数为不确定数量,个数需要在调用的 ...

  9. Spring代理模式(jdk动态代理模式)

    有动态代理和静态代理: 静态代理就是普通的Java继承调用方法. Spring有俩种动态代理模式:jdk动态代理模式 和 CGLIB动态代理 jdk动态代理模式: 代码实现: 房东出租房子的方法(继承 ...

  10. 回文自动机pam

    目的:类似回文Trie树+ac自动机,可以用来统计一些其他的回文串相关的量 复杂度:O(nlogn) https://blog.csdn.net/Lolierl/article/details/999 ...