使用多线程很容易,但是如果多个线程同时访问一个共享资源时而不加以控制,就会导致数据损坏。所以多线程并发时,必须要考虑线程同步(或称线程安全)的问题。 

什么是线程同步

多个线程同时访问共享资源时,使多个线程顺序(串行)访问共享资源的机制。
注意:
1,共享资源,比如全局变量和静态变量。
2,访问,一般指写操作,读操作无需考虑线程同步。
3,串行,指当一个线程正在访问共享资源时,其它线程等待,直到该线程释放锁。

线程同步带来哪些问题

如果能保证多个线程不会同时访问共享资源,那么就不需要考虑线程同步。
虽然线程同步能保证多线程同时访问共享数据时线程安全,但是同时也会带来以下问题:
1,使用起来繁琐,因为必须找出代码中所有可能由多个线程同时访问的共享数据,并且要用额外的代码将这些代码包围起来,获取和释放一个线程同步锁,而一旦有一处忘记用锁包围,共享数据就会被损坏。
2,损害性能,因为获取和释放一个锁是需要时间的。
3,可能会造成更多的线程被创建,由于线程同步锁一次只允许一个线程访问共享资源,当线程池线程试图获取一个暂时无法获取的锁时,线程池就会创建一个新的线程。
所以,要从设计上尽可能地避免线程同步,实在不能避免的再考虑线程同步。

线程同步的常用解决方案

1,锁

包括lock关键字和Monitor类型。
使用lock关键字实现:

 1 /// <summary>
 2 /// 线程同步计算器
 3 /// </summary>
 4 public class SyncCounter : CounterBase
 5 {
 6     /// <summary>
 7     /// 全局变量
 8     /// </summary>
 9     public int Result = 0;
10
11     private static readonly object lockObj = new object();
12
13     public override void Increase()
14     {
15         lock (lockObj)
16         {
17             Result++;
18         }
19     }
20
21     public override void Decrease()
22     {
23         lock (lockObj)
24         {
25             Result--;
26         }
27     }
28 }

View Code

需要注意的是:
1,lock锁定的对象必须是引用类型,不能是值类型。因为值类型传入会发生装箱,这样每次lock的将是一个不同的对象,就没有办法实现多线程同步了。
2,避免使用public类型的对象,这样很容易导致死锁。因为其它代码也有可能锁定该对象。
3,避免锁定字符串,因为字符串会被CLR暂留(也就是说两个变量的字符串内容相同,.net会把暂留的字符串对象分配给变量),导致应用程序中锁定的是同一个对象,造成死锁。
使用Monitor实现:

 1 /// <summary>
 2 /// 线程同步计算器
 3 /// </summary>
 4 public class SyncCounter : CounterBase
 5 {
 6     /// <summary>
 7     /// 全局变量
 8     /// </summary>
 9     public int Result = 0;
10
11     private static readonly object lockObj = new object();
12
13     public override void Increase()
14     {
15         Monitor.Enter(lockObj);
16         try
17         {
18             Result++;
19         }
20         finally
21         {
22             Monitor.Exit(lockObj);
23         }
24     }
25
26     public override void Decrease()
27     {
28         Monitor.Enter(lockObj);
29         try
30         {
31             Result--;
32         }
33         finally
34         {
35             Monitor.Exit(lockObj);
36         }
37     }
38 }

View Code

完整代码:

  1 namespace ConsoleApplication28
  2 {
  3     class Program
  4     {
  5         static void Main(string[] args)
  6         {
  7             //同时发起3个异步线程
  8             Console.WriteLine("普通(非线程同步)计算器测试...");
  9             var normalCounter = new NormalCounter();
 10             var tasks = new List<Task>();
 11             var task1 = Task.Factory.StartNew(() =>
 12             {
 13                 TestCounter(normalCounter);
 14             });
 15             tasks.Add(task1);
 16
 17             var task2 = Task.Factory.StartNew(() =>
 18             {
 19                 TestCounter(normalCounter);
 20             });
 21             tasks.Add(task2);
 22
 23             var task3 = Task.Factory.StartNew(() =>
 24             {
 25                 TestCounter(normalCounter);
 26             });
 27             tasks.Add(task3);
 28
 29
 30             Task.WaitAll(tasks.ToArray());
 31             Console.WriteLine("NormalCounter.Result:" + normalCounter.Result);
 32             Console.WriteLine("*******************************************");
 33
 34             Console.WriteLine("线程同步计算器测试...");
 35             var syncCounter = new SyncCounter();
 36             var tasks1 = new List<Task>();
 37             task1 = Task.Factory.StartNew(() =>
 38             {
 39                 TestCounter(syncCounter);
 40             });
 41             tasks1.Add(task1);
 42
 43             task2 = Task.Factory.StartNew(() =>
 44             {
 45                 TestCounter(syncCounter);
 46             });
 47             tasks1.Add(task2);
 48
 49             task3 = Task.Factory.StartNew(() =>
 50             {
 51                 TestCounter(syncCounter);
 52             });
 53             tasks1.Add(task3);
 54
 55             Task.WaitAll(tasks1.ToArray());
 56             Console.WriteLine("SyncCounter.Result:" + syncCounter.Result);
 57
 58             Console.ReadKey();
 59         }
 60
 61         /// <summary>
 62         ///
 63         /// </summary>
 64         /// <param name="counter"></param>
 65         static void TestCounter(CounterBase counter)
 66         {
 67             //100000次加减
 68             for (int i = 0; i < 100000; i++)
 69             {
 70                 counter.Increase();
 71                 counter.Decrease();
 72             }
 73         }
 74     }
 75
 76     /// <summary>
 77     /// 计算器基类
 78     /// </summary>
 79     public abstract class CounterBase
 80     {
 81         /// <summary>
 82         /// 加
 83         /// </summary>
 84         public abstract void Increase();
 85
 86         /// <summary>
 87         /// 减
 88         /// </summary>
 89         public abstract void Decrease();
 90     }
 91
 92     /// <summary>
 93     /// 普通计算器
 94     /// </summary>
 95     public class NormalCounter : CounterBase
 96     {
 97         /// <summary>
 98         /// 全局变量
 99         /// </summary>
100         public int Result = 0;
101
102         public override void Increase()
103         {
104             Result++;
105         }
106
107         public override void Decrease()
108         {
109             Result--;
110         }
111
112     }
113
114     /// <summary>
115     /// 线程同步计算器
116     /// </summary>
117     public class SyncCounter : CounterBase
118     {
119         /// <summary>
120         /// 全局变量
121         /// </summary>
122         public int Result = 0;
123
124         private static readonly object lockObj = new object();
125
126         public override void Increase()
127         {
128             lock (lockObj)
129             {
130                 Result++;
131             }
132         }
133
134         public override void Decrease()
135         {
136             lock (lockObj)
137             {
138                 Result--;
139             }
140         }
141     }
142 }

View Code

lock关键字揭密:

通过查看lock关键字生成的IL代码,如下图:

从上图可以得出以下结论:

lock关键字内部就是使用Monitor类(或者说lock关键字是Monitor的语法糖),使用lock关键字比直接使用Monitor更好,原因有二。

1,lock语法更简洁。

2,lock确保了即使代码抛出异常,也可以释放锁,因为在finally中调用了Monitor.Exit方法。 

2,信号同步

信号同步机制中涉及的类型都继承自抽象类WaitHandle,这些类型有EventWaitHandle(类型化为AutoResetEvent、ManualResetEvent)和Semaphore以及Mutex。关系如下图。

下面是使用信号同步机制的一个简单的例子,如下代码:

 1 namespace WindowsFormsApplication1
 2 {
 3     public partial class Form1 : Form
 4     {
 5         //信号
 6         AutoResetEvent autoResetEvent = new AutoResetEvent(false);
 7
 8         public Form1()
 9         {
10             InitializeComponent();
11
12             CheckForIllegalCrossThreadCalls = false;
13         }
14
15         /// <summary>
16         /// 开始
17         /// </summary>
18         /// <param name="sender"></param>
19         /// <param name="e"></param>
20         private void button1_Click(object sender, EventArgs e)
21         {
22             Task.Factory.StartNew(() =>
23             {
24                 this.richTextBox1.Text+="线程启动..." + Environment.NewLine;
25                 this.richTextBox1.Text += "开始处理一些实际的工作" + Environment.NewLine;
26                 Thread.Sleep(3000);
27
28                 this.richTextBox1.Text += "我开始等待别的线程给我信号,才愿意继续下去" + Environment.NewLine;
29                 autoResetEvent.WaitOne();
30                 this.richTextBox1.Text += "我继续做一些工作,然后结束了!";
31             });
32         }
33
34         /// <summary>
35         /// 信号同步
36         /// </summary>
37         /// <param name="sender"></param>
38         /// <param name="e"></param>
39         private void button2_Click(object sender, EventArgs e)
40         {
41             //给在autoResetEvent上等待的线程一个信号
42             autoResetEvent.Set();
43         }
44     }
45 }

View Code

运行效果:

1,线程阻塞,等待信号。

2,主线程发送信号,让线程继续执行。

3,线程安全的集合类

我们也可以通过使用.net提供的线程安全的集合类来保证线程安全。在命名空间:System.Collections.Concurrent下。
主要包括:
  • ConcurrentQueue 线程安全版本的Queue【常用】
  • ConcurrentStack线程安全版本的Stack
  • ConcurrentBag线程安全的对象集合
  • ConcurrentDictionary线程安全的Dictionary【常用】

多线程(6)线程同步相关推荐

  1. 3、Linux多线程,线程同步(转)

    3.Linux多线程,线程同步 5)线程私有数据 进程内的所有线程共享进程的数据空间,因此全局变量为所有线程所共有.但有时线程也需要保存自己的私有数据,这时可以创建线程私有数据(Thread-spec ...

  2. C#笔记20:多线程之线程同步中的信号量AutoResetEvent和ManualResetEvent

    C#笔记20:多线程之线程同步中的信号量AutoResetEvent和ManualResetEvent 本章概要: 1:终止状态和非终止状态 2:AutoResetEvent和ManualResetE ...

  3. VC++ MFC 多线程及线程同步(详细、全面总结!)

    更多详情:http://blog.csdn.net/whyacinth/ VC++ MFC 多线程及线程同步 关键词: MFC    多线程及线程同步                          ...

  4. Java多线程之线程同步机制(锁,线程池等等)

    Java多线程之线程同步机制 一.概念 1.并发 2.起因 3.缺点 二.三大不安全案例 1.样例一(模拟买票场景) 2.样例二(模拟取钱场景) 3.样例三(模拟集合) 三.同步方法及同步块 1.同步 ...

  5. MFC 多线程及线程同步

    一.MFC对多线程编程的支持 MFC中有两类线程,分别称之为工作者线程和用户界面线程.二者的主要区别在于工作者线程没有消息循环,而用户界面线程有自己的消息队列和消息循环. 工作者线程没有消息机制,通常 ...

  6. cocos2dx多线程以及线程同步 与 cocos2dx内存管理与多线程问题

    cocos2d-x引擎在内部实现了一个庞大的主循环,每帧之间更新界面,如果耗时的操作放到了主线程中,游戏的界面就会卡,这是不能容忍的,游戏最基本的条件就是流畅性,这就是为什么游戏开发选择C++的原因. ...

  7. 多线程,线程同步,synchronized关键字的用法

    一.什么是多线程 Java多线程实现方式主要有四种:继承Thread类.实现Runnable接口.实现Callable接口通过FutureTask包装器来创建Thread线程.使用ExecutorSe ...

  8. NET多线程探索-线程同步和通信

    NET中各种线程同步方法 在NET多线程开发中,有时候需要多个线程协调工作,完成这个步骤的过程称为"同步". 使用同步的主要原因: 1.多个线程访问同一个共享资源. 2.多线程写入 ...

  9. Java多线程(线程同步)

    多线程编程很容易出现"错误情况",这是由系统的线程调度具有一定的随机性造成的,不过即使程序偶然出现问题,那也是由于编程不当引起.使用多个线程访问同一个数据时很容易出现此类状况,因此 ...

最新文章

  1. opencv 环境变量
  2. java 中调用window系统中的文件,或者执行命令(shell、.CMD、.EXE)并获取返回值
  3. 原创 | OpenAPI 标准规范
  4. cocostudio 实现换行功能的label (文本区) lua
  5. 阿里数据库内核月报:2016年03月
  6. 期待!小米电视5官曝新功能:可准确识别家中每一个人
  7. Figma插件开发-生成Gif
  8. Java练手项目(好玩又有趣)
  9. linux下codeblock使用注意事项 (deepin)
  10. 国家网络信息安全战略三步曲
  11. delphi技巧--分离汉字和英文字母
  12. 前端学习-jquery-实现点击button对文本的add及detele
  13. likeshop单商户SAAS商城系统无限多开
  14. Cherry键盘外接Mac command按键失灵
  15. 【博士论文】深度学习的对抗攻击与鲁棒性测评
  16. windows如何截屏
  17. MOOS-ivp简介
  18. 路由器交换机存储部件浅析
  19. C语言超出类型数值范围的表示方法
  20. Ubuntu子系统上安装miniconda.sh 438、444报错

热门文章

  1. c 语言中浮点数舍入,浮点数在C中舍入,我不明白为什么
  2. matplotlib 中文_详解Matplotlib中文字符显示问题
  3. python web框架对比_Python六大开源框架对比
  4. 无线对讲调度服务器,无线对讲系统解决方案
  5. mq多个消费者消费一个消息_一个普通消费者的米家产品使用感受
  6. java mp4 视频时间戳_MP4文件中音视频时间戳的计算
  7. linux软件包管理工具,Linux 软件包管理器-----yum配置详解一
  8. php+include+引入html文件,include引入文件
  9. java文件客户端下载_使用Java写一个minio的客户端上传下载文件
  10. FilterListener笔记