本笔记摘抄自: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. 2019牛客暑期多校训练营(第九场) E-All men are brothers(并查集+组合数学)

    >传送门< 题意:最初有 n个人且互不认识,接下来 m行,每行有 x,y表示x和y交朋友,朋友关系满足自反性和传递性,每次输出当前选取4个人且互不认识的方案数. 思路:比赛的时候知道是用并 ...

  2. 深入理解.NET Core的基元(三) - 深入理解runtimeconfig.json

    原文:Deep-dive into .NET Core primitives, part 3: runtimeconfig.json in depth 作者:Nate McMaster 译文:深入理解 ...

  3. centos7环境搭建一台mysql服务器启动多个端口

    在一台服务器上启动多个mysql实例,分别用不同的端口号,因centos7版本安装mysql5.7后不存在mysqld_multi .mysqld_safe等命令,做踩坑总结 Mysql多实例实现的3 ...

  4. jsp html 实现隐藏输入框,点击可以取消隐藏&&弹出输入框

    jsp代码: <script language="javascript" type="text/javascript"> function chg ...

  5. rug

    rug()函数 :给图添加rug representation. · 一维的 · rug的方式是补充,仅仅包括落在图像区域内的x的值,丢失掉任何有限的值,将会被警告:而丢失任何非有限的值,则静静地丢失 ...

  6. Python如何爬取实时变化的WebSocket数据

    一.前言 作为一名爬虫工程师,在工作中常常会遇到爬取实时数据的需求,比如体育赛事实时数据.股市实时数据或币圈实时变化的数据.如下图: Web 领域中,用于实现数据'实时'更新的手段有轮询和 WebSo ...

  7. 转:Eclipse中创建Maven版的Web工程(详解)

    一.搭建步骤 ♦首先创建一个Maven的Project,如下图: ♦点击Next,勾选 Create a simple project ♦点击Next,注意Packing要选择war,因为我们创建的是 ...

  8. luogu P2759 奇怪的函数 |二分答案

    题目描述 使得 x^x达到或超过 n 位数字的最小正整数 x 是多少? 输入格式 一个正整数 n 输出格式 使得 x^x达到 n 位数字的最小正整数 x 计算一个数有多少位 log10(x)+1 #i ...

  9. IOS原生方法实现二维码生成与扫描

    转自:http://www.jianshu.com/p/d6663245d3fa 二维码的生成有好多第三方库,如Z-Xing.但是为了控制安装包的大小,或者并不需要其他的一些额外的功能,用系统的方法即 ...

  10. 【解决】error pulling image configuration: Get https:// ...... x509: certificate has expired or is not yet valid

    [问题]进行镜像拉取时报错 [分析] 很多人会被第一句所蒙蔽,按照网上教程进行修改etc/sysconfig/docker,之后发现还是没有用. 其实这里重点是最后一句"certificat ...