上一章节中和大家分享了线程的基础使用方法。在这一章中来和大家分享线程的一些常用方法。
主要包括:线程阻塞,线程终止,线程锁三方面。
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();
输出结果:
新闻热点
疑难解答