原文网址:http://blog.csdn.net/undoner/article/details/12849661
在这篇文章里,我们首先阐述什么是同步,不同步有什么问题,然后讨论可以采取哪些措施控制同步,接下来我们会仿照回顾网络通信时那样,构建一个服务器端的“线程池”,JDK为我们提供了一个很大的concurrent工具包,最后我们会对里面的内容进行探索。
为什么要线程同步?
说到线程同步,大部分情况下, 我们是在针对“单对象多线程”的情况进行讨论,一般会将其分成两部分,一部分是关于“共享变量”,一部分关于“执行步骤”。
共享变量
当我们在线程对象(Runnable)中定义了全局变量,run方法会修改该变量时,如果有多个线程同时使用该线程对象,那么就会造成全局变量的值被同时修改,造成错误。我们来看下面的代码:
1 class MyRunner implements Runnable
2 {
3 public int sum = 0;
4
5 public void run()
6 {
7 System.out.println(Thread.currentThread().getName() + " Start.");
8 for (int i = 1; i <= 100; i++)
9 {
10 sum += i;
11 }
12 try {
13 Thread.sleep(500);
14 } catch (InterruptedException e) {
15 e.printStackTrace();
16 }
17 System.out.println(Thread.currentThread().getName() + " --- The value of sum is " + sum);
18 System.out.println(Thread.currentThread().getName() + " End.");
19 }
20 }
21
22
23 private static void sharedVaribleTest() throws InterruptedException
24 {
25 MyRunner runner = new MyRunner();
26 Thread thread1 = new Thread(runner);
27 Thread thread2 = new Thread(runner);
28 thread1.setDaemon(true);
29 thread2.setDaemon(true);
30 thread1.start();
31 thread2.start();
32 thread1.join();
33 thread2.join();
34 }
这个示例中,线程用来计算1到100的和是多少,我们知道正确结果是5050(好像是高斯小时候玩过这个?),但是上述程序返回的结果是10100,原因是两个线程同时对sum进行操作。
执行步骤
我们在多个线程运行时,可能需要某些操作合在一起作为“原子操作”,即在这些操作可以看做是“单线程”的,例如我们可能希望输出结果的样子是这样的:
1 线程1:步骤1
2 线程1:步骤2
3 线程1:步骤3
4 线程2:步骤1
5 线程2:步骤2
6 线程2:步骤3
如果同步控制不好,出来的样子可能是这样的:
线程1:步骤1
线程2:步骤1
线程1:步骤2
线程2:步骤2
线程1:步骤3
线程2:步骤3
这里我们也给出一个示例代码:
1 class MyNonSyncRunner implements Runnable
2 {
3 public void run() {
4 System.out.println(Thread.currentThread().getName() + " Start.");
5 for(int i = 1; i <= 5; i++)
6 {
7 System.out.println(Thread.currentThread().getName() + " Running step " + i);
8 try
9 {
10 Thread.sleep(50);
11 }
12 catch(InterruptedException ex)
13 {
14 ex.printStackTrace();
15 }
16 }
17 System.out.println(Thread.currentThread().getName() + " End.");
18 }
19 }
20
21
22 private static void syncTest() throws InterruptedException
23 {
24 MyNonSyncRunner runner = new MyNonSyncRunner();
25 Thread thread1 = new Thread(runner);
26 Thread thread2 = new Thread(runner);
27 thread1.setDaemon(true);
28 thread2.setDaemon(true);
29 thread1.start();
30 thread2.start();
31 thread1.join();
32 thread2.join();
33 }
如何控制线程同步
既然线程同步有上述问题,那么我们应该如何去解决呢?针对不同原因造成的同步问题,我们可以采取不同的策略。
控制共享变量
我们可以采取3种方式来控制共享变量。
将“单对象多线程”修改成“多对象多线程”
上文提及,同步问题一般发生在“单对象多线程”的场景中,那么最简单的处理方式就是将运行模型修改成“多对象多线程”的样子,针对上面示例中的同步问题,修改后的代码如下:
1 private static void sharedVaribleTest2() throws InterruptedException
2 {
3 Thread thread1 = new Thread(new MyRunner());
4 Thread thread2 = new Thread(new MyRunner());
5 thread1.setDaemon(true);
6 thread2.setDaemon(true);
7 thread1.start();
8 thread2.start();
9 thread1.join();
10 thread2.join();
11 }
我们可以看到,上述代码中两个线程使用了两个不同的Runnable实例,它们在运行过程中,就不会去访问同一个全局变量。
将“全局变量”降级为“局部变量”
既然是共享变量造成的问题,那么我们可以将共享变量改为“不共享”,即将其修改为局部变量。这样也可以解决问题,同样针对上面的示例,这种解决方式的代码如下:
1 class MyRunner2 implements Runnable
2 {
3 public void run()
4 {
5 System.out.println(Thread.currentThread().getName() + " Start.");
6 int sum = 0;
7 for (int i = 1; i <= 100; i++)
8 {
9 sum += i;
10 }
11 try {
12 Thread.sleep(500);
13 } catch (InterruptedException e) {
14 e.printStackTrace();
15 }
16 System.out.println(Thread.currentThread().getName() + " --- The value of sum is " + sum);
17 System.out.println(Thread.currentThread().getName() + " End.");
18 }
19 }
20
21
22 private static void sharedVaribleTest3() throws InterruptedException
23 {
24 MyRunner2 runner = new MyRunner2();
25 Thread thread1 = new Thread(runner);
26 Thread thread2 = new Thread(runner);
27 thread1.setDaemon(true);
28 thread2.setDaemon(true);
29 thread1.start();
30 thread2.start();
31 thread1.join();
32 thread2.join();
33 }
我们可以看出,sum变量已经由全局变量变为run方法内部的局部变量了。
使用ThreadLocal机制
ThreadLocal是JDK引入的一种机制,它用于解决线程间共享变量,使用ThreadLocal声明的变量,即使在线程中属于全局变量,针对每个线程来讲,这个变量也是独立的。
我们可以用这种方式来改造上面的代码,如下所示:
1 class MyRunner3 implements Runnable
2 {
3 public ThreadLocal tl = new ThreadLocal();
4
5 public void run()
6 {
7 System.out.println(Thread.currentThread().getName() + " Start.");
8 for (int i = 0; i <= 100; i++)
9 {
10 if (tl.get() == null)
11 {