6语法基础_多线程
基础概念:
进程:计算机概念,一个程序在运行的时候所占据的资源,就像qq一样就是一个进程,而多开QQ,就是多开进程
线程:计算机概念, QQ,里面的各种聊天 其实就是新开的一个线程
进程和线程:线程属于进程,进程销毁了,线程也就没了,qq关闭了,聊天窗口也就没有了
句柄:描述程序中的某一个最小单元,是一个long数字,操作系统通过这个数字识别应用程序。
多线程:计算概念,就是某一个进程中,多个线程同时运行;
1.请问异步和多线程的区别
多线程就是实现异步的方法 异步只是一个概念 。所以一般说到异步:就必然需要从主线程之外开启一个新的线程去执行别的方法
2.什么是异步回调:
就是发起请求后,不等待响应就去先干别的事 我煮饭后,不等饭煮完,先去偷吃菜
3.为什么计算机可以多线程
因为有多个CPU的关系就可以同时进行计算
单个线程其实可以模拟多线程,循环去执行分片,但是某一个进程卡死了 那么循环就会卡死
4.为什么异步方法会比同步方法快
同步方法 :CPU利用率低 耗时长 所以性能会慢
异步方法:CPU利用率低高 耗时短 所以性能会快 但是这是用资源换性能 同时因为异步是需要多线程去执行的,线程调度策略也是需要耗费资源的
5.为什么线程启动没有顺序,
因为线程是向操作系统申请的 由操作系统调度策略来决定的 ,但是可以通过BeginInvoke等一些回调 等待来进行顺序控制
6.Task和Thread的区别
Thread .net 1.0出现 功能强大,但是开发者用不好 反而会影响代码,没有控制线程数量 线程数量=计算机资源
ThreadPool .net 2.0出现, 池化思想: 如果某个对象创建和销毁代价比较高,同时这个对象还可以反复使用的,就需要一个池子 保存多个这样的对象,需要用的时候从池子里面获取;用完之后不用销毁,放回池子;(享元模式) 节约资源提升性能;此外,还能管控总数量,防止滥用; 循环利用,节能减排
Task .net 3.0出现, .net多线程最佳实现
7.什么时候需要使用多线程
需要任务并发的时候 以资源换性能,不卡界面 改善用户体验
8.多线程最好不要断点调试,用输出的结果+线程ID来判断
9.线程安全原理本质上就是排他锁的实现
异步方法
委托方法+BeginInvoke开启一个异步方法
private void btnAsync_Click(object sender, EventArgs e) { Action<string> action = this.DoSomethingLong; for (int i = 0; i < 5; i++) { action.BeginInvoke("btnAsync_Click", null, null); } } private void DoSomethingLong(string name) { Thread.Sleep(2000);//线程等待 }
委托方法+BeginInvoke开启一个异步方法 并且有回调
//异步结果 IAsyncResult asyncResult = null; //回调方法 AsyncCallback callback = Callback => { Thread.Sleep(5000); Console.WriteLine($"这里是beginInvoke的第三个参数{Callback.AsyncState}"); }; Action<string> action = this.DoSomethingLong; asyncResult = action.BeginInvoke("btnAsyncAdvanced_Click", callback, "自定义参数");
委托方法+BeginInvoke+IsCompleted开启一个异步方法 并且有回调,等到异步完成,如需要进度条显示
//异步结果 IAsyncResult asyncResult = null; //回调方法 AsyncCallback callback = Callback => { Thread.Sleep(5000); Console.WriteLine($"这里是beginInvoke的第三个参数{Callback.AsyncState}"); }; Action<string> action = this.DoSomethingLong; asyncResult = action.BeginInvoke("btnAsyncAdvanced_Click", callback, "自定义参数"); //2、IsCompleted 完成等待 { int i = 0; while (!asyncResult.IsCompleted) { if (i < 9) { Console.WriteLine($"正在玩命为你加载中。。。已经完成{++i * 10}%"); } else { Console.WriteLine($"正在玩命为你加载中。。。已经完成99.9999%"); } Thread.Sleep(200); } Console.WriteLine("加载完成。。。"); }
委托方法+BeginInvoke+WaitOne 等待异步方法完成
//异步结果 IAsyncResult asyncResult = null; //回调方法 AsyncCallback callback = Callback => { Thread.Sleep(5000); Console.WriteLine($"这里是beginInvoke的第三个参数{Callback.AsyncState}"); }; Action<string> action = this.DoSomethingLong; asyncResult = action.BeginInvoke("btnAsyncAdvanced_Click", callback, "自定义参数"); //WaitOne等待 //asyncResult.AsyncWaitHandle.WaitOne();//一直等待任务完成 //asyncResult.AsyncWaitHandle.WaitOne(-1);//一直等待任务完成 //asyncResult.AsyncWaitHandle.WaitOne(3000);//最多等待3000ms,如果超时了,就不等待了
委托方法+BeginInvoke+EndInvoke 等待异步方法完成 并且获取委托方法的返回值
Func<int> func = () => { //Thread.Sleep(5000); return DateTime.Now.Year; }; func.Invoke(); IAsyncResult asyncResult1 = func.BeginInvoke(ar => { func.EndInvoke(ar); }, null); int iResult = func.EndInvoke(asyncResult1); Console.WriteLine(iResult);
Thread
Thread.Start()开启新线程的第一种方法
Thread thread = new Thread(threadStart); thread.Start(); //开启一个新线程 thread.Suspend();// 暂停线程 thread.Resume();//恢复 无法实时的去暂停或者恢复线程 thread.Abort();//终结线程 Thread.ResetAbort();//都会有延时
Thread 等待线程,设置前后台线程 设置线程优先的概率
thread.Join();//可以限时等待 thread.Join(2000); //可以限时等待 //设置线程的优先概率,不一定百分百保证先执行 thread.Priority = ThreadPriority.Highest; thread.IsBackground = true;//为后台线程 进程结束,线程结束了 thread.IsBackground = false; //前台线程 进程结束后,任务执行完毕以后,线程才结束
Thread+Fun<> 获取返回结果
{ //Thread开启一个新的线程执行任务,如何获取返回结果: //定义一个 int返回值的委托 Func<int> func = () => { Thread.Sleep(5000); return DateTime.Now.Year; }; Func<int> FuncResult = this.ThreadWithReturn(func);//开启线程执行委托方法 int iResult = FuncResult.Invoke();//如果需要得到执行结果,是必须要等待的 } private Func<T> ThreadWithReturn<T>(Func<T> func) { T t = default(T); ThreadStart threadStart = new ThreadStart(() => { t = func.Invoke(); }); Thread thread = new Thread(threadStart); thread.Start(); return new Func<T>(() => { thread.Join(); return t; }); }
ThreadPool 线程池
线程池是全局,Task,async/awit 都是来自于线程池 不轻易设置,线程池里面的线程是有调度策略的,所以一般不去进行设置线程池里面的线程数量
ThreadPool.QueueUserWorkItem 开启一个线程
ThreadPool.QueueUserWorkItem(o => { Console.WriteLine($"**************** {Thread.CurrentThread.ManagedThreadId.ToString("00")} {DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss.fff")}***************"); this.DoSomethingLong("ThreadPool.QueueUserWorkItem1");//开启了一个线程 });
ThreadPool.QueueUserWorkItem 开启一个线程 传入参数
ThreadPool.QueueUserWorkItem(o => { Console.WriteLine($"第二个参数:{o}"); this.DoSomethingLong("ThreadPool.QueueUserWorkItem1");//开启了一个线程 }, "参数");
Task
Task开启线程的三种方式
Console.WriteLine("主线程开始"); Task task = new Task(() => { Console.WriteLine("开启一个新线程"); }); task.Start(); Task.Run(() => { Console.WriteLine("开启第二个新线程"); }); Task.Factory.StartNew(() => { Console.WriteLine("开启第三个新线程"); });
卡界面 waitall:阻塞主线程,要等所有线程完成才往下执行
卡界面 waitany:等待某一个线程完成了,那么就会继续往下走 应用场景: 一个数据需要从 缓存 数据库 api获取 可以开启三个线程去 谁先获取到 就用谁
List<Task> tasksList = new List<Task>(); tasksList.Add(Task.Run(() => { Console.WriteLine("开启第1个新线程"); })); tasksList.Add(Task.Run(() => { Console.WriteLine("开启第2个新线程"); })); tasksList.Add(Task.Run(() => { Console.WriteLine("开启第3个新线程"); })); Task[] tasks1 = new Task[0]; tasks1[0] = Task.Run(() => { Console.WriteLine("开启第1个新线程"); }); //waitall:阻塞主线程,要等所有线程完成才往下执行 Task.WaitAll(tasksList.ToArray());//要获取的是一个task数组 Task.WaitAll(tasks1);//要获取的是一个task数组 //waitany:等待某一个线程完成了,那么就会往下走下去去 Task.WaitAny(tasksList.ToArray());//要获取的是一个task数组 Task.WaitAny(tasks1);//要获取的是一个task数组
ContinueWhenAny ContinueWhenAll 非阻塞式的回调;使用的线程可能是新线程,也可能是刚从线程池回收完毕的线程,唯一不可能就是去使用主线程
Console.WriteLine("开始线程"); Console.WriteLine("开始线程"); TaskFactory taskFactory = new TaskFactory(); List<Task> taskList = new List<Task>(); taskList.Add(taskFactory.StartNew(() => Console.WriteLine("子线程1"))); taskList.Add(taskFactory.StartNew(() => Console.WriteLine("子线程2"))); taskList.Add(taskFactory.StartNew(() => Console.WriteLine("子线程3"))); taskList.Add(taskFactory.StartNew(() => Console.WriteLine("子线程4"))); taskList.Add(taskFactory.StartNew(() => Console.WriteLine("子线程5"))); //找到子线程最先完成的 taskFactory.ContinueWhenAny(taskList.ToArray(), t => Console.WriteLine($"最先完成的子线程{Thread.CurrentThread.ManagedThreadId.ToString("00")}")); //所有子线程都完成了 taskList.Add(taskFactory.ContinueWhenAll(taskList.ToArray(), rArray => Console.WriteLine($"开发都完成,一起庆祝一下{Thread.CurrentThread.ManagedThreadId.ToString("00")}")));
Task.Delay 延迟执行
// Task.Delay 出现于4.5版本 Stopwatch stopwatch = new Stopwatch(); stopwatch.Start(); Task task = Task.Delay(2000).ContinueWith(t => //任务在2000ms 以后执行 { stopwatch.Stop(); Console.WriteLine($"{stopwatch.ElapsedMilliseconds}"); Console.WriteLine("回调已完成"); }); } }
Task 获取返回值 会卡界面
//如果获取返回值 Task<int> result = Task.Run<int>(() => { Thread.Sleep(2000); return DateTime.Now.Year; }); int iResult = result.Result; //会卡界面等待
又想获取异步回调结果,又不想卡界面,可以如下操作
using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Threading; using System.Threading.Tasks; namespace ConsoleApp2 { class Program { static void Main(string[] args) { //有想要异步回调获取返回值 又不卡界面 只能花样实现 Console.WriteLine($"1:{DateTime.Now}"); Func<int> func = () => { Console.WriteLine($"2:{DateTime.Now}"); Thread.Sleep(3000); Console.WriteLine($"3:{DateTime.Now}"); return DateTime.Now.Year; }; Console.WriteLine($"4:{DateTime.Now}"); Func<int> func1Rusult = ThreadWithReturen<int>(func); //不卡界面 { Console.WriteLine($"5:{DateTime.Now}"); Console.WriteLine("假如这里的执行也需要3秒钟."); } Console.WriteLine($"6:{DateTime.Now}上面都不会卡界面"); { Console.WriteLine($"所以可以在这里做点操作 做一些主线程耗时的运动,"); } int iRsult = func1Rusult.Invoke(); //这里会卡界面 最终其实还是会卡界面的 只不过让卡界面放到后面来执行, Console.WriteLine($"7:{DateTime.Now}"); } /// <summary> /// 一个新的线程来执行委托里的动作 /// </summary> /// <typeparam name="T"></typeparam> /// <param name="func"></param> /// <returns></returns> private static Func<T> ThreadWithReturen<T>(Func<T> func) { Console.WriteLine($"8:{DateTime.Now}"); T t = default(T); ThreadStart threadStart = () => { Console.WriteLine($"9:{DateTime.Now}"); t = func.Invoke(); }; Console.WriteLine($"11:{DateTime.Now}"); Thread thread = new Thread(threadStart); thread.Start();//不卡界面 Console.WriteLine($"12:{DateTime.Now}"); return new Func<T>(() => { Console.WriteLine($"13:{DateTime.Now}"); thread.Join(); Console.WriteLine($"14:{DateTime.Now}"); return t; }); } } }
Task.run.ContinueWith 线程完成后再开启一个延续任务
Task.Run<int>(() => { Thread.Sleep(2000); return DateTime.Now.Year; }).ContinueWith(intT => //开启一个延续任务 { int i = intT.Result; });
Parallel 对Task进一步进行了封装 .Netframework 4.5版本出来
1.Parallel主线程会参与计算 所以会卡顿界面,一般工作中就是使用它控制一下线程数量
2.Task.waitAll+主线程参与计算=Parallel
3.并发执行action,所以它也是多线程
{ //Parallel 主要可以控制线程数量 //Parallel并发执行了五个委托,开启了新线程,主线程参与计算,界面会阻塞 // Task WaitAll + 主线程 Parallel.Invoke(() => { this.DoSomethingLong("btnParallel_Click_1"); }, () => { this.DoSomethingLong("参数"); }, () => { this.DoSomethingLong("参数"); }, () => { this.DoSomethingLong("参数"); }, () => { this.DoSomethingLong("参数"); }); } { ParallelOptions parallelOptions = new ParallelOptions(); parallelOptions.MaxDegreeOfParallelism = 2;//设置线程最大并发数量为两个 //for循环十个委托方法,每次只会用两个线程跑 Parallel.For(0, 10, parallelOptions, t => this.DoSomethingLong($"btnParallel_Click_{t}")); } { ////控制线程数量 ParallelOptions parallelOptions = new ParallelOptions(); parallelOptions.MaxDegreeOfParallelism = 2; //控制线程的最大数量 //控制执行数量 Parallel.ForEach(new int[] { 12, 13, 14, 15, 16, 17 }, parallelOptions, t => this.DoSomethingLong($"btnParallel_Click_{t}")); }
Parallel 怎么让主线程不参与计算 并且设置最大线程数量
//如何使主线程不参与计算 就是用子线程包一层 Task.Run(() => { ParallelOptions parallelOptions = new ParallelOptions(); parallelOptions.MaxDegreeOfParallelism = 5; Parallel.For(0, 10, parallelOptions, i => Console.WriteLine("1111111")); });
await/async
1.await/async 是语法糖,成对出现 要使用的话 是需要.net fromworkd 4.5以上的版本
一定要懂 await/async执行顺序(需要去解读async执行的源码,本质就是状态机+线程上下文来控制的)
2.方法运行的时候 只要遇到 await 就会直接返回去执行主线程的方法
所以和同步执行没区别,都是串行化执行(串行化意味着不能接受并发请求,要一个个等待执行,就不能提高性能),,但是可以提高吞吐量(响应能力,1秒能处理多少个请求--web模式)
3.await执行后_继续执行的代码都是由主线程来执行
4.await 开启一个新线程执行里面一个方法 await后面的方法相当于异步回调一样
5.有了task那么async的价值在哪里, 为什么要推荐使用async 而不是task?
async比Task。性能方面并不会提升 该是执行多少秒就是多少秒 但是每秒钟的时候 task可以有1000并发, async比task多个几百
Task: 同时十个人访问一个资源,task是需要开启十个线程,当需要结果返回的时候Task是需要一个线程去等待结果
async/await:同时十个人访问一个资源 ,async不一定会开启十个线程,主线程走下去遇到await就返回,而await之后需要执行的可能是主线程和子线程1,2,或者子线程2执行完了,又让子线程2执行:相当于遇到await直接发送命令,并且不需要消费一个线程去等待这个结果-结果完成之前就不浪费CPU资源。完成后才用线程来处理,而且还能重用一些被回收了的线程。所以就能提高吞吐量 长时间需要等待的结果,交给硬件,而不需要CPU参与 async使用的就是硬件DMA技术,所以才可以直接发命令
语法糖,同步方式写异步,增加系统吞吐量,要用就从头到底都用async,web开发推荐
适合场景:跟第三方交互的(非托管资源,大多数就有async版本)
数据库 openasync-redis。webapi-请求,文件读取
不适合场景 服务器本地计算(CPU密集型,托管资源,不管怎么样CPu都要干的活,async就无法偷懒,发再多的命令过去,CPU都是要执行这么久,反而async状态机还会一直切换)用了也没事,因为async很成熟,大数据加减乘数,数据处理
下面执行方法输出是 1 3 2 4 5 6
private async static Task Test() { { Console.WriteLine($"1 主线程id={Thread.CurrentThread.ManagedThreadId.ToString("00")}"); Method(); Console.WriteLine($"2 主线程id={Thread.CurrentThread.ManagedThreadId}"); } Console.Read(); } /// </summary> private static async void Method() { //主线程执行 Console.WriteLine($"3 主线程ID={Thread.CurrentThread.ManagedThreadId}"); Task task = Task.Run(() =>//启动新线程完成任务 { Console.WriteLine($"4,子线程ID={Thread.CurrentThread.ManagedThreadId}"); Thread.Sleep(1000); Console.WriteLine($"5 子线程ID={Thread.CurrentThread.ManagedThreadId}"); }); await task; //主线程执行 Console.WriteLine($"6 子线程ID={Thread.CurrentThread.ManagedThreadId}"); }
Task t = NoReturnTask(); Console.WriteLine($"Main Thread Task ManagedThreadId={Thread.CurrentThread.ManagedThreadId}"); t.Wait();//主线程等待Task的完成 阻塞的 await t;//await后的代码会由线程池的线程执行 非阻塞 /// <summary> /// 无返回值 async Task == async void /// Task和Task<T>能够使用await, Task.WhenAny, Task.WhenAll等方式组合使用。Async Void 不行 /// </summary> /// <returns></returns> private static async Task NoReturnTask() //在async/await方法里面如果没有返回值,默认返回一个Task { //这里还是主线程的id Console.WriteLine($"NoReturnTask Sleep before await,ThreadId={Thread.CurrentThread.ManagedThreadId}"); Task task = Task.Run(() => { Console.WriteLine($"NoReturnTask Sleep3000 before,ThreadId={Thread.CurrentThread.ManagedThreadId}"); Thread.Sleep(1000); Console.WriteLine($"NoReturnTask Sleep3000 after,ThreadId={Thread.CurrentThread.ManagedThreadId}"); }); await task; Console.WriteLine($"NoReturnTask Sleep after await,ThreadId={Thread.CurrentThread.ManagedThreadId}"); //return; //return new TaskFactory().StartNew(() => { }); //不能return 没有async才行 }
//如果要得到返回值就必须要等待的 Task<long> t = SumAsync(); Console.WriteLine($"Main Thread Task ManagedThreadId={Thread.CurrentThread.ManagedThreadId}"); long lResult = t.Result;//访问result 主线程等待所有的任务完成 //如果访问Result,就相当于是同步方法! t.Wait();//等价于上一行 /// <summary> /// 带返回值的Task /// 要使用返回值就一定要等子线程计算完毕 /// </summary> /// <returns>async 就只返回long</returns> private static async Task<long> SumAsync() { Console.WriteLine($"SumAsync 111 start ManagedThreadId={Thread.CurrentThread.ManagedThreadId}"); long result = 0; await Task.Run(() => { for (int k = 0; k < 10; k++) { Console.WriteLine($"SumAsync {k} await Task.Run ManagedThreadId={Thread.CurrentThread.ManagedThreadId}"); Thread.Sleep(1000); } for (long i = 0; i < 999_999_999; i++) { result += i; } }); Console.WriteLine($"SumFactory 111 end ManagedThreadId={Thread.CurrentThread.ManagedThreadId}"); return result; }
Task<int> t = SumFactory(); Console.WriteLine($"Main Thread Task ManagedThreadId={Thread.CurrentThread.ManagedThreadId}"); long lResult = t.Result;//没有await和async 普通的task t.Wait(); /// 要使用返回值就一定要等子线程计算完毕 /// </summary> /// <returns>没有async Task</returns> private static Task<int> SumFactory() { Console.WriteLine($"SumFactory 111 start ManagedThreadId={Thread.CurrentThread.ManagedThreadId}"); TaskFactory taskFactory = new TaskFactory(); Task<int> iResult = taskFactory.StartNew<int>(() => { Thread.Sleep(3000); Console.WriteLine($"SumFactory 123 Task.Run ManagedThreadId={Thread.CurrentThread.ManagedThreadId}"); return 123; }); //Console.WriteLine($"This is {iResult.Result}"); Console.WriteLine($"SumFactory 111 end ManagedThreadId={Thread.CurrentThread.ManagedThreadId}"); return iResult; }
多线程异常处理
第一种方法:等待所以异常完成,去捕捉每个异常的线程
try { List<Task> taskList = new List<Task>(); for (int i = 0; i < 50; i++) { string name = $"btnThreadCore_Click_{i}"; int k = i; taskList.Add(Task.Run(() => { try { if (k == 5) { throw new Exception($"{name} {Thread.CurrentThread.ManagedThreadId.ToString("00")} 异常了 "); } Console.WriteLine($"this is {name} {Thread.CurrentThread.ManagedThreadId.ToString("00")} Ok!"); } catch (Exception) { Console.WriteLine("异常"); } })); }; Task.WaitAll(taskList.ToArray()); //如果这里不等待 try -catch 能否捕捉到异常 ,不能 //只有WaitAll 之后才能捕捉到所有的异常信息 //在实际开发中是不允许在子线程中出现异常的 } catch (AggregateException aex) {//循环获取具体某个异常线程 foreach (var exception in aex.InnerExceptions) { Console.WriteLine(exception.Message); } } catch (Exception ex) { Console.WriteLine(ex.Message); throw; }
第二种方法:有异常线程之后,直接取消剩下的线程
{ // 1 创建cts 共享变量 2 try-catch 捕捉异常 3 在开启的线程中 判断 CancellationTokenSource cts = new CancellationTokenSource(); //线程安全 //cts 有个状态值 IsCancellationRequested 默认初始化位false 改为True之后不能再改回false //提供一个Cancel() 方法改变IsCancellationRequested状态(不能修改) try { List<Task> taskList = new List<Task>(); for (int i = 0; i < 50; i++) { string name = $"btnThreadCore_Click_{i}"; int k = i; Thread.Sleep(new Random().Next(50, 100)); //休息五到十秒 taskList.Add(Task.Run(() => { try { if (!cts.IsCancellationRequested) {//获取线程是否已经被取消 } } catch (Exception ex) { cts.Cancel();//设置 IsCancellationRequested为false Console.WriteLine(ex.Message); } }, cts.Token));// cts.Token让未启动的线程直接取消 }; Task.WaitAll(taskList.ToArray()); } catch (AggregateException aex) { foreach (var exception in aex.InnerExceptions) { Console.WriteLine(exception.Message); } } catch (Exception ex) { Console.WriteLine(ex.Message); } }
多线程里面的临时变量问题
for (int i = 0; i < 20; i++) //for 循环很快 { Task.Run(() => // { Console.WriteLine($"btnThreadCore_Click_{i} 线程Id={Thread.CurrentThread.ManagedThreadId.ToString("00")}"); //这个输出的i为什么是20? 因为for循环非常快,等循环完成之后线程才开始执行 而且线程还会延迟启动 //所以可能人家循环了4次之后,线程才开启 }); } //循环里面 开启线程没进行阻塞,代码执行又很快,线程又是延迟启动 所以 for (int i = 0; i =< 10000; i++) { int k = i; //输出的 i不一定是10000,一个线程都还没开启 循环就已经完成了 //输出的 k 一定是10000 每一次都是一个新的变量 闭包 只对当前作用域代码生效 Task.Run(() => { Console.WriteLine($"k={k} i={i} 线程Id={Thread.CurrentThread.ManagedThreadId.ToString("00")}"); }); }
多线程安全
多线程为什么会出现线程安全:
在全局变量/共享变量/磁盘文件/静态变量/数据库的值 只要是开启多线程去访问修改的时候,就有可能出现线程安全
多线程安全策略 加锁(锁的作用:排他):使用 private static readonly object Obj_Lock = new object(); 锁 避免多个线程同时并发使用
for (int i = 0; i < 100000; i++) { Task.Run(() => { try { lock (Obj_Lock)//可以 避免多线程并发,如果锁住以后,其实这里跟单线程基本上没啥区别; { this.NumTow += 1; } } catch (Exception) { throw; } }); }
不要使用 private string Str_Lock = "Richard";
当两个String的值同样如: string A1="字母" string A2="字母" 其实它们指向的是同一个对象
现在我们给 A1 A2 加锁 每人5个线程去跑, 当执行A1的时候 A2并不能执行,因为锁住的是同一个对象,
10个线程去执行他们 最终执行的流程是:
A1开启一个新线程去执行,
A2等待A1完成后,A2开启一个先执行,A1又在等待A2
这样一个一个线程去跑 这样既有线程开销,还和单线程一样
所以实际锁住的还是同一个对象 而在实际工作中,String值相同的情况时有发生,而出现问题不容易发现。
以下是string锁例子 其实和没锁一样,
for (int i = 0; i < 100000; i++) { this.NumOne += 1; } for (int i = 0; i < 100000; i++) { Task.Run(() => { try { lock (Str_Lock)//可以 避免多线程并发,如果锁住以后,其实这里跟单线程基本上没啥区别; { this.NumTow += 1; } } catch (Exception) { throw; } }); }
还有不建议使用This锁,
lock(this)的缺点:
1:就是在一个线程锁定某对象之后导致整个对象无法被其他线程访问。
2:This 表示当前的实例 因为通常无法控制的其他人可能会锁定该对象。 所以极大可能造成死锁
3:任何引用对象的人都可以在对象设计者/创建者不知道它的情况下锁定它。这增加了多线程解决方案的复杂性,并可能影响其正确性。
多线程安全策略 线程安全集合 并行操作的集合应运而生了。
System.Collections.Concurrent.ConcurrentStack 基于线程安全 当我们对应List集合操作的时候 一个读取,一个删除更新,这是不允许的
所以当要使用并发操作一个集合的时候 可以使用 这个线程安全集合是没有加锁的,所以也不会出现死锁的情况
多线程安全策略 数据分拆,
把10个并发访问一个数据的时候,分拆成多个小数据,避免多个线程去操作同一数据 安全又高效率 大型项目适用
本文来自博客园,作者:12不懂3,转载请注明原文链接:https://www.cnblogs.com/LZXX/p/13087088.html