线程Thread基础学习(1)
学习过操作系统的人员对于线程一词并不陌生,或多或少或深或浅都有了解,但对于程序员来说,只有了解是不行的,在应聘工作的面试中或多或少总有面试官提到这些问题,此问题涉及领域并不宽,但作用着实不小,特别是在系统性能方面。在多核处理器盛行的今天,多线程成为面试官比较喜欢的话题。方式多为并发的理解,多线程的同步等等。
为了能在工作有能有立足之地,程序员必须每天学习,不断学习新技术,新知识等等一切关于开发的新知识。每天不必学太多,只要有进步就行。为了不让自己的学习过手就忘,我把学习过程记录在这里,也是能把自己的学习经历,遇到的问题和大家分享讨论,有错误的地方还请大家指出,多多指教,目的是做到基础知识一步一个脚印,稳步前行。好了,不多说了,步入正题。
以下定义一个测试线程类:
1 using System; 2 using System.Threading; 3 4 namespace Demo.ProcessThread 5 { 6 public class TestThread 7 { 8 //静态引用类型字段 9 static object obj = new object(); 10 // 实例引用类似字段 11 object obj2 = new object(); 12 //静态方法 13 public static void TestNoLock1() 14 { 15 for (int i = 0; i < 10; i++) 16 { 17 Console.WriteLine(string.Format("TestNoLock1:{0}", i)); 18 } 19 } 20 public static void TestNoLock2() 21 { 22 for (int i = 0; i < 10; i++) 23 { 24 Console.WriteLine(string.Format("TestNoLock2:{0}", i)); 25 } 26 } 27 } 28 }
在这里分情况讨论:
1.没有使用lock关键字情况,线程之间没有同步信息。
1 namespace Demo.ProcessThread 2 { 3 class Program 4 { 5 static void Main(string[] args) 6 { 7 ThreadStart ts1 = new ThreadStart(TestThread.TestNoLock1); 8 Thread t1 = new Thread(ts1); 9 ThreadStart ts2 = new ThreadStart(TestThread.TestNoLock2); 10 Thread t2 = new Thread(ts2); 11 t1.Start(); 12 t2.Start(); 13 } 14 } 15 }
第一次执行结果:
TestNoLock2:0
TestNoLock2:1
TestNoLock2:2
TestNoLock2:3
TestNoLock2:4
TestNoLock1:0
TestNoLock1:1
TestNoLock1:2
TestNoLock1:3
TestNoLock1:4
TestNoLock1:5
TestNoLock1:6
TestNoLock1:7
TestNoLock1:8
TestNoLock1:9
TestNoLock2:5
TestNoLock2:6
TestNoLock2:7
TestNoLock2:8
TestNoLock2:9
请按任意键继续. . .
第二次执行结果:
TestNoLock2:0
TestNoLock2:1
TestNoLock2:2
TestNoLock2:3
TestNoLock2:4
TestNoLock2:5
TestNoLock2:6
TestNoLock2:7
TestNoLock2:8
TestNoLock1:0
TestNoLock1:1
TestNoLock1:2
TestNoLock1:3
TestNoLock1:4
TestNoLock1:5
TestNoLock1:6
TestNoLock1:7
TestNoLock1:8
TestNoLock1:9
TestNoLock2:9
请按任意键继续. . .
两次结果完全不同,也没有规律可循,这就是线程的并发。多个线程之间如果不做任何处理,它们的执行时互不干扰的,所以结果每次输出都不同。这一步没有错,但是如果是多个线程共享同一个变量或存储空间,大家想想如果线程之间不做控制会出现什么情况?改造一下TestNoLock1和TestNoLock2方法,看结果:
1 //静态字段 2 public static int count = 0; 3 public static void TestNoLock1() 4 { 5 for (int i = 0; i < 10; i++) 6 { 7 count++; 8 Console.WriteLine(TestThread.count); 9 } 10 } 11 public static void TestNoLock2() 12 { 13 for (int i = 0; i < 10; i++) 14 { 15 count--; 16 Console.WriteLine(TestThread.count); 17 } 18 }
测试:
1 ThreadStart ts1 = new ThreadStart(TestThread.TestNoLock1); 2 Thread t1 = new Thread(ts1); 3 ThreadStart ts2 = new ThreadStart(TestThread.TestNoLock2); 4 Thread t2 = new Thread(ts2); 5 t1.Start(); 6 t2.Start();
执行结果:
1
1
2
3
0
3
2
1
0
-1
-2
-3
-4
-5
4
-4
-3
-2
-1
0
请按任意键继续. . .
这肯定不是你想要的结果,并且每次执行结果还不相同。为了得到正确的结果,必须控制并发的执行,这时候lock关键字就要粉墨登场了(方法很多,这里只讨论lock,后续的会陆续介绍其他方法),好了,看代码,继续改造TestNoLock1和TestNoLock2方法:
1 //静态引用类型字段 2 static object obj = new object(); 3 public static void TestNoLock1() 4 { 5 lock (obj) 6 { 7 for (int i = 0; i < 10; i++) 8 { 9 count++; 10 Console.WriteLine(TestThread.count); 11 } 12 } 13 } 14 public static void TestNoLock2() 15 { 16 lock (obj) 17 { 18 for (int i = 0; i < 10; i++) 19 { 20 count--; 21 Console.WriteLine(TestThread.count); 22 } 23 } 24 }
再看执行结果:
1
2
3
4
5
6
7
8
9
10
9
8
7
6
5
4
3
2
1
0
请按任意键继续. . .
怎么样,是想要的结果吧,这就是lock的作用,lock可以同步多线程之间共享信息,当lock锁定一个对象时,其他进程在进行lock操作,就是发生线程阻塞,知道lock对象被释放,才会继续执行。
接下来再看lock锁定的对象的特征:
1.假如把obj的类型换成值类型(int,datetime)等,会发现有语法错误提示,“xx不是lock语句要求引用类型”,
归纳:lock对象必须是引用类型,因为引用类型和值类型传值过程不同,引用类型传递地址,值类型传递副本,故不能作为Lock对象。
2.在TestThread类中在定义两个实例方法,代码如下:
1 //静态引用类型字段 2 static object obj = new object(); 3 // 实例方法 4 public void Test1() 5 { 6 // lock 必须锁定引用类型参数 7 lock (obj) 8 { 9 for (int i = 0; i < 10; i++) 10 { 11 Console.WriteLine(string.Format("Test1:{0}", i)); 12 } 13 } 14 } 15 public void Test2() 16 { 17 // lock 必须锁定引用类型参数 18 lock (obj) 19 { 20 for (int i = 0; i < 10; i++) 21 { 22 Console.WriteLine(string.Format("Test2:{0}", i)); 23 } 24 } 25 }
测试代码:
1 ThreadStart tStart = new ThreadStart(new TestThread().Test1); 2 Thread thread1 = new Thread(tStart); 3 4 ThreadStart tStart2 = new ThreadStart(new TestThread().Test2); 5 Thread thread2 = new Thread(tStart2); 6 thread1.Start(); 7 thread2.Start();
测试结果:多次执行结果相同,也是正确的执行结果:
Test1:0
Test1:1
Test1:2
Test1:3
Test1:4
Test1:5
Test1:6
Test1:7
Test1:8
Test1:9
Test2:0
Test2:1
Test2:2
Test2:3
Test2:4
Test2:5
Test2:6
Test2:7
Test2:8
Test2:9
请按任意键继续. . .
接着改造lock锁定对象为实例字段,把static关键字取消,代码如下:
1 //静态引用类型字段 2 object obj = new object();
再看执行结果:
Test2:0
Test2:1
Test2:2
Test2:3
Test2:4
Test2:5
Test2:6
Test1:0
Test1:1
Test1:2
Test1:3
Test1:4
Test1:5
Test1:6
Test1:7
Test1:8
Test1:9
Test2:7
Test2:8
Test2:9
请按任意键继续. . .
顺序全乱了,并且每次执行的结果无法预期的。那么如何才能得到正确的结果呢?继续往下看,把测试代码改为:
1 TestThread testThread = new TestThread(); 2 ThreadStart tStart = new ThreadStart(testThread.Test1); 3 Thread thread1 = new Thread(tStart); 4 5 ThreadStart tStart2 = new ThreadStart(testThread.Test2); 6 Thread thread2 = new Thread(tStart2); 7 thread1.Start(); 8 //thread1.Join(); // 可以确保thread1线程全部执行完毕,否则无限期阻塞 9 thread2.Start();
看看结果是不是有顺序了,归纳:不同的进程lock的对象不许是指同一个对象,否则不起作用。
好了,这篇文章就先写到这里。