網站首頁 編程語言 正文
一、前言
我們先來看下面一個例子:
using System; using System.Threading; namespace ThreadSynchDemo { class Program { private static int Counter = 0; static void Main(string[] args) { Thread t1 = new Thread(() => { for (int i = 0; i < 1000; i++) { Counter++; Thread.Sleep(1); } }); t1.Start(); Thread t2 = new Thread(() => { for (int i = 0; i < 1000; i++) { Counter++; Thread.Sleep(1); } }); t2.Start(); Thread.Sleep(3000); Console.WriteLine(Counter); Console.ReadKey(); } } }
我們猜想一下程序的輸出結果是多少?2000?我們運行程序看一下輸出結果:
我們看到,程序最后輸出的結果跟我們預測的完全不一樣,這是什么原因呢?這就是由線程同步引起的問題。
線程同步問題:是解決多個線程同時操作一個資源的問題
。
在上面的例子中,t1和t2兩個線程里面都是讓變量Counter的值自增1,假設這時t1線程讀取到Counter的值為200,可能t2線程執行非常快,t1線程讀取Counter值的時候,t2線程已經把Counter的值改為了205,等t1線程執行完畢以后,Counter的值又被變為了201,這樣就會出現線程同步的問題了。那么該如何解決這個問題呢?
二、解決線程同步問題
1、lock
解決線程同步問題最簡單的是使用lock。lock可以解決多個線程同時操作一個資源引起的問題。lock是C#中的關鍵字,它要鎖定一個資源,lock的特點是:同一時刻只能有一個線程進入lock的對象的范圍,其它lock的線程都要等待。我們看下面優化后的代碼:
using System; using System.Threading; namespace ThreadSynchDemo { class Program { private static int Counter = 0; // 定義一個locker對象 private static Object locker = new Object(); static void Main(string[] args) { #region 存在線程同步問題 //Thread t1 = new Thread(() => { // for (int i = 0; i < 1000; i++) // { // Counter++; // Thread.Sleep(1); // } //}); //t1.Start(); //Thread t2 = new Thread(() => { // for (int i = 0; i < 1000; i++) // { // Counter++; // Thread.Sleep(1); // } //}); //t2.Start(); #endregion #region 使用Lock解決線程同步問題 Thread t1 = new Thread(() => { for (int i = 0; i < 1000; i++) { lock(locker) { Counter++; } Thread.Sleep(1); } }); t1.Start(); Thread t2 = new Thread(() => { for (int i = 0; i < 1000; i++) { lock (locker) { Counter++; } Thread.Sleep(1); } }); t2.Start(); #endregion Thread.Sleep(3000); Console.WriteLine(Counter); Console.ReadKey(); } } }
這時我們在運行程序,查看輸出結果:
這時輸出結果是正確的。
注意:lock只能鎖住同一個對象,如果是不同的對象,還是會有線程同步的問題。lock鎖定的對象必須是引用類型的對象。
我們在定義一個Object類型的對象,lock分別鎖住兩個對象,看看是什么結果:
using System; using System.Threading; namespace ThreadSynchDemo { class Program { private static int Counter = 0; // 定義一個locker對象 private static Object locker = new Object(); // 定義locker2 private static Object locker2 = new Object(); static void Main(string[] args) { #region 存在線程同步問題 //Thread t1 = new Thread(() => { // for (int i = 0; i < 1000; i++) // { // Counter++; // Thread.Sleep(1); // } //}); //t1.Start(); //Thread t2 = new Thread(() => { // for (int i = 0; i < 1000; i++) // { // Counter++; // Thread.Sleep(1); // } //}); //t2.Start(); #endregion #region 使用Lock解決線程同步問題 //Thread t1 = new Thread(() => { // for (int i = 0; i < 1000; i++) // { // lock(locker) // { // Counter++; // } // Thread.Sleep(1); // } //}); //t1.Start(); //Thread t2 = new Thread(() => { // for (int i = 0; i < 1000; i++) // { // lock (locker) // { // Counter++; // } // Thread.Sleep(1); // } //}); //t2.Start(); #endregion #region 使用lock鎖住不同的對象也會有線程同步問題 Thread t1 = new Thread(() => { for (int i = 0; i < 1000; i++) { lock (locker) { Counter++; } Thread.Sleep(1); } }); t1.Start(); Thread t2 = new Thread(() => { for (int i = 0; i < 1000; i++) { lock (locker2) { Counter++; } Thread.Sleep(1); } }); t2.Start(); #endregion Thread.Sleep(3000); Console.WriteLine(Counter); Console.ReadKey(); } } }
程序運行結果:
可以看到,這時還是會有線程同步的問題。雖然使用了lock,但是我們鎖住的是不同的對象,這樣也會有線程同步問題。lock必須鎖住同一個對象才可以。
我們下面在來看一個多線程同步問題的例子:
using System; using System.Threading; namespace ThreadSynchDemo2 { class Program { static int Money = 100; ////// 定義一個取錢的方法 /// /// static void QuQian(string name) { Console.WriteLine(name + "查看一下余額" + Money); int yue = Money - 1; Console.WriteLine(name + "取錢"); Money = yue; Console.WriteLine(name + "取完了,剩" + Money); } static void Main(string[] args) { Thread t1 = new Thread(() => { for (int i = 0; i < 10; i++) { QuQian("t2"); } }); Thread t2 = new Thread(() => { for (int i = 0; i < 10; i++) { QuQian("t2"); } }); t1.Start(); t2.Start(); t1.Join(); t2.Join(); Console.WriteLine("余額" + Money); Console.ReadKey(); } } }
我們看一下輸出結果:
可以看到,最終的余額并不是80,這也是線程同步帶來的問題,如何解決。解決思路就是使用同步的技術避免兩個線程同時修改一個余額。
2、最大粒度——同步方法
在方法上面使用[MethodImpl(MethodImplOptions.Synchronized)],標記該方法是同步方法,這樣一個方法只能同時被一個線程訪問。我們在QuQian的方法上面標記,修改后的代碼如下:
using System; using System.Runtime.CompilerServices; using System.Threading; namespace ThreadSynchDemo2 { class Program { static int Money = 100; ////// 定義一個取錢的方法,在上面標記為同步方法 /// /// [MethodImpl(MethodImplOptions.Synchronized)] static void QuQian(string name) { Console.WriteLine(name + "查看一下余額" + Money); int yue = Money - 1; Console.WriteLine(name + "取錢"); Money = yue; Console.WriteLine(name + "取完了,剩" + Money); } static void Main(string[] args) { Thread t1 = new Thread(() => { for (int i = 0; i < 10; i++) { QuQian("t2"); } }); Thread t2 = new Thread(() => { for (int i = 0; i < 10; i++) { QuQian("t2"); } }); t1.Start(); t2.Start(); t1.Join(); t2.Join(); Console.WriteLine("余額" + Money); Console.ReadKey(); } } }
程序輸出結果:
現在的方法就是“線程安全”的了。什么是“線程安全”呢?“線程安全”是指方法可以被多個線程隨意調用,而不會出現混亂。如果出現了混亂,那么就是“線程不安全”的。“線程安全”的方法可以在多線程里面隨意的使用。
3、對象互斥鎖
對象互斥鎖就是我們上面講的lock。我們在用lock來修改上面QuQian的例子:
using System; using System.Runtime.CompilerServices; using System.Threading; namespace ThreadSynchDemo2 { class Program { static int Money = 100; ////// 定義一個取錢的方法,在上面標記為同步方法 /// /// //[MethodImpl(MethodImplOptions.Synchronized)] //static void QuQian(string name) //{ // Console.WriteLine(name + "查看一下余額" + Money); // int yue = Money - 1; // Console.WriteLine(name + "取錢"); // Money = yue; // Console.WriteLine(name + "取完了,剩" + Money); //} private static object locker = new object(); static void QuQian(string name) { Console.WriteLine(name + "查看一下余額" + Money); int yue = Money - 1; Console.WriteLine(name + "取錢"); Money = yue; Console.WriteLine(name + "取完了,剩" + Money); } static void Main(string[] args) { Thread t1 = new Thread(() => { for (int i = 0; i < 10; i++) { // 使用對象互斥鎖 lock(locker) { QuQian("t1"); } } }); Thread t2 = new Thread(() => { for (int i = 0; i < 10; i++) { lock (locker) { QuQian("t2"); } } }); t1.Start(); t2.Start(); t1.Join(); t2.Join(); Console.WriteLine("余額" + Money); Console.ReadKey(); } } }
程序輸出結果:
可以看到,最終的輸出結果還是80。
同一時刻只能有一個線程進入同一個對象的lock代碼塊。必須是同一個對象才能起到互斥的作用。lock后必須是引用類型,不一定是object,只要是對象就行。
鎖對象選擇很重要,選不對就起不到同步的作用;選不對還有可能會造成其他地方被鎖,比如用字符串做鎖(因為字符串緩沖池導致導致可能用的是其他地方正在使用的鎖),所以不建議使用字符串做鎖。下面的代碼就是不允許的:
lock("locker")
兩個方法如果都用一個對象做鎖,那么訪問A的時候就不能訪問B,因此鎖選擇很重要。
4、Monitor
其實lock關鍵字就是對Monitor的簡化調用,lock最終會被編譯成Monitor,因此一般不直接使用Monitor類,看下面代碼:
using System; using System.Threading; namespace MonitorDemo { class Program { static int Money = 100; private static object locker = new object(); static void QuQian(string name) { // 等待沒有人鎖定locker對象,就鎖定它,然后繼續執行 Monitor.Enter(locker); try { Console.WriteLine(name + "查看一下余額" + Money); int yue = Money - 1; Console.WriteLine(name + "取錢"); Money = yue; Console.WriteLine(name + "取完了,剩" + Money); } finally { // 釋放locker對象的鎖 Monitor.Exit(locker); } } static void Main(string[] args) { Thread t1 = new Thread(() => { for (int i = 0; i < 10; i++) { QuQian("t1"); } }); Thread t2 = new Thread(() => { for (int i = 0; i < 10; i++) { QuQian("t2"); } }); t1.Start(); t2.Start(); t1.Join(); t2.Join(); Console.WriteLine("余額" + Money); Console.ReadKey(); } } }
程序輸出結果:
Monitor類里面還有TryEnter方法,如果Enter的時候有人在占用鎖,它不會等待,而是會返回false。看下面的示例代碼:
using System; using System.Threading; namespace MonitorDemo { class Program { static int Money = 100; private static object locker = new object(); static void QuQian(string name) { // 等待沒有人鎖定locker對象,就鎖定它,然后繼續執行 Monitor.Enter(locker); try { Console.WriteLine(name + "查看一下余額" + Money); int yue = Money - 1; Console.WriteLine(name + "取錢"); Money = yue; Console.WriteLine(name + "取完了,剩" + Money); } finally { // 釋放locker對象的鎖 Monitor.Exit(locker); } } static void F1(int i) { if (!Monitor.TryEnter(locker)) { Console.WriteLine("有人在鎖著呢"); return; } Console.WriteLine(i); Monitor.Exit(locker); } static void Main(string[] args) { //Thread t1 = new Thread(() => { // for (int i = 0; i < 10; i++) // { // QuQian("t1"); // } //}); //Thread t2 = new Thread(() => { // for (int i = 0; i < 10; i++) // { // QuQian("t2"); // } //}); Thread t1 = new Thread(() => { for (int i = 0; i < 10; i++) { F1(i); } }); Thread t2 = new Thread(() => { for (int i = 0; i < 10; i++) { F1(i); } }); t1.Start(); t2.Start(); t1.Join(); t2.Join(); Console.WriteLine("余額" + Money); Console.ReadKey(); } } }
程序輸出結果:
原文鏈接:https://www.cnblogs.com/dotnet261010/p/12335538.html
相關推薦
- 2022-06-14 C#獲取指定目錄下某種格式文件集并備份到指定文件夾_C#教程
- 2023-02-02 C++實現延遲的方法詳解_C 語言
- 2022-08-15 VPP靜態映射實現DNAT
- 2022-07-18 Qt和Windows消息通信機制
- 2022-03-07 android?studio?項目?:UI設計高精度實現簡單計算器_Android
- 2021-12-11 C語言SetConsoleCursorPosition函數使用方法_C 語言
- 2022-04-30 Python自定義指標聚類實例代碼_python
- 2023-03-28 Python代碼庫之Tuple如何append添加元素問題_python
- 最近更新
-
- window11 系統安裝 yarn
- 超詳細win安裝深度學習環境2025年最新版(
- Linux 中運行的top命令 怎么退出?
- MySQL 中decimal 的用法? 存儲小
- get 、set 、toString 方法的使
- @Resource和 @Autowired注解
- Java基礎操作-- 運算符,流程控制 Flo
- 1. Int 和Integer 的區別,Jav
- spring @retryable不生效的一種
- Spring Security之認證信息的處理
- Spring Security之認證過濾器
- Spring Security概述快速入門
- Spring Security之配置體系
- 【SpringBoot】SpringCache
- Spring Security之基于方法配置權
- redisson分布式鎖中waittime的設
- maven:解決release錯誤:Artif
- restTemplate使用總結
- Spring Security之安全異常處理
- MybatisPlus優雅實現加密?
- Spring ioc容器與Bean的生命周期。
- 【探索SpringCloud】服務發現-Nac
- Spring Security之基于HttpR
- Redis 底層數據結構-簡單動態字符串(SD
- arthas操作spring被代理目標對象命令
- Spring中的單例模式應用詳解
- 聊聊消息隊列,發送消息的4種方式
- bootspring第三方資源配置管理
- GIT同步修改后的遠程分支