上一章節中和大家分享了線程的基礎使用方法。在這一章中來和大家分享線程的一些常用方法。
主要包括:線程阻塞,線程終止,線程鎖三方面。
Console.WriteLine("主線程執行時間:{0}", DateTime.Now.ToString());Thread.Sleep(4000); //阻塞4sConsole.WriteLine("主線程執行時間:{0}", DateTime.Now.ToString());
輸出結果:
兩次打印輸出間隔為:4秒。線程阻塞以毫秒為單位。
Sleep也支持TimeSpan,將當前線程阻塞指定的時間。
第一次看到msdn的解釋一下子沒有反應過來。這里我們可以理解為:分別開啟三個線程t1,t2,t3對t1,t2,t3依次調用Join后,程序會先把線程t1執行完后,在執行線程t2的內容..以此類推到t3。
如下代碼所示:
1 var watch = Stopwatch.StartNew(); 2 Thread t1 = new Thread(() => 3 { 4 Thread.Sleep(4000); 5 Console.WriteLine("t1 is ending."); 6 }); 7 t1.Start(); 8 t1.Join(); 9 Console.WriteLine("t1.Join() returned.");10 11 Thread t2 = new Thread(() =>12 {13 Thread.Sleep(1000);14 Console.WriteLine("t2 is ending.");15 });16 t2.Start();17 t2.Join();18 Console.WriteLine("t2.Join() returned.");19 20 Console.WriteLine("總結:Join()會阻塞調用線程直到調用線程結束." + watch.ElapsedMilliseconds);
輸出結果:
程序先執行線程t1里的內容,讓線程阻塞4秒,因為線程t1調用Join()方法阻塞調用線程,直到t1線程執行完成。
然后打印出“t1.Join() returned.”。在執行t2線程,直到t2線程執行完后才執行主線程打印的內容。
這里不難看出他們是按順序來執行的。
如果我們不使用join()方法看看他的輸出結果會是怎么樣:
var watch = Stopwatch.StartNew();Thread t1 = new Thread(() =>{ Thread.Sleep(4000); Console.WriteLine("t1 is ending.");});t1.Start();//t1.Join();Console.WriteLine("t1.Join() returned.");Thread t2 = new Thread(() =>{ Thread.Sleep(1000); Console.WriteLine("t2 is ending.");});t2.Start();//t2.Join();Console.WriteLine("t2.Join() returned.");Console.WriteLine("總結:Join()會阻塞調用線程直到調用線程結束." + watch.ElapsedMilliseconds);
輸出結果:
此時主線程會先開啟t1線程,t1被阻塞4秒 。所以t1線程里的內容沒有被打印出來,會在4秒后打印。
這時主線程不會等待t1線程完成后在執行下面代碼,主線程會繼續向下執行打印出“t1.Join() returned.”
然后開啟t2線程,t2線程同樣也會被阻塞了1秒。
主線程會繼續向下執行打印出其他內容。最后陸續由線程t2,線程t1打印出各自對應信息。
1 Thread t1 = new Thread(() => 2 { 3 for (int i = 0; i < 4; i++) 4 { 5 try 6 { 7 Thread.Sleep(400); 8 } 9 catch (ThreadAbortException ex)10 {11 Console.WriteLine("Abort終止線程.當前線程名稱:{0}.狀態:{1}", Thread.CurrentThread.Name, Thread.CurrentThread.ThreadState);12 }13 Console.WriteLine("我在運行著!");14 }15 16 });17 t1.Name = "t1";18 t1.Start();19 Thread.Sleep(1000);20 t1.Abort();21 Console.WriteLine("當前線程名稱:{0}.狀態:{1}", t1.Name, t1.ThreadState);
輸出結果:
開啟t1線程,阻塞800毫秒打印了二次“我在運行著!”,準備運行第三次時。
阻塞1000毫秒的主線程調用Abort()方法直接把t1線程給干掉了.他再也沒有站起來執行第四次打印。
當前t1線程直接被干掉。
1 Thread t2 = new Thread(() => 2 { 3 for (int i = 0; i < 4; i++) 4 { 5 try 6 { 7 Thread.Sleep(400); 8 Console.WriteLine("我在運行著!"); 9 }10 catch (ThreadInterruptedException ex)11 {12 Console.WriteLine("Interrupt終止線程.當前線程名稱:{0}.狀態:{1}", Thread.CurrentThread.Name, Thread.CurrentThread.ThreadState);13 }14 }15 });16 t2.Name = "t2";17 t2.Start();18 Thread.Sleep(1000);19 t2.Interrupt();20 Console.WriteLine("當前線程名稱:{0}.狀態:{1}", t2.Name, t2.ThreadState);21 Console.Read();
輸出結果:
開啟t2線程,阻塞800毫秒打印了二次“我在運行著!”,準備運行第三次時。
阻塞1000毫秒的主線程調用Interrupt()方法把t2線程第三次阻塞中斷,但t2線程并未被終止,繼續在運行。
直到線程運行結束。
先來看一下多線程在訪問共享變量未加鎖的情況:
1 int number = 0; 2 //沒加鎖 3 for (int i = 0; i < 10; i++) 4 { 5 new Thread(() => 6 { 7 Thread.Sleep(1000); //堵塞線程.不然線程執行時間太短,體現不出并發效果 8 Console.WriteLine(number); 9 number++;10 }).Start();11 }
輸出結果:
我們發現開啟10個線程去訪問一個共享變量number,在沒有加鎖的情況下有5個線程訪問到的值都是:0。
當多個線程存在并發的時,難免會碰到相互沖突的事情。這個時候我們就會用到鎖。
Lock()方法在MSIL中會被編譯成 Monitor.Enter()和Monitor.Exit()。
在來看看多線程在訪問共享變量加鎖的情況:
1 //加鎖 2 int number = 0; 3 object objLock = new object(); 4 for (int i = 0; i < 10; i++) 5 { 6 new Thread(() => 7 { 8 Thread.Sleep(100); //堵塞線程.不然線程執行時間太短,體現不出并發效果 9 10 Monitor.Enter(objLock);11 Console.WriteLine(number);12 number++;13 Monitor.Exit(objLock);14 }).Start();15 }
輸出結果:
加鎖后我們發現多線程在訪問共享變量采用的是排他模式。
每次訪問共享變量都只有一個線程,其他線程只能等待別的線程訪問完成后才能進行訪問。[Monitor.Enter()和Monitor.Exit()必須是成對使用.]
1 object objLock = new object(); 2 new Thread(() => 3 { 4 Thread.Sleep(1000); 5 Monitor.Enter(objLock); 6 7 Console.WriteLine("我是第一個出現"); 8 Console.WriteLine("我是第二個出現"); 9 Monitor.Wait(objLock);10 Console.WriteLine("完成1");11 Monitor.Pulse(objLock);12 13 Monitor.Exit(objLock);14 15 }).Start();16 17 18 new Thread(() =>19 {20 Thread.Sleep(2000);21 Monitor.Enter(objLock);22 23 Monitor.Pulse(objLock);24 Console.WriteLine("我是第三個出現");25 Console.WriteLine("我是第四個出現");26 Monitor.Wait(objLock);27 Console.WriteLine("完成2");28 29 Monitor.Exit(objLock);30 }).Start();
輸出結果:
新聞熱點
疑難解答