线程安全的定义:当多个线程访问某个类时,不管运行环境采用何种调度方式或者这些线程如何交替执行,并且在主调用代码中不需要额外的同步和协调,这个类都能表现出正确的行为,那么这个类就是线程安全的。
线程安全性可能是非常复杂的,在没有充足同步的情况下,多个线程中的操作执行顺序是不可预测的,甚至会产生非常奇怪的结果。
如果当多个线程访问一个可变的共享变量时没有使用合适的同步,那么线程就会出现错误。有三种方式可以修复这个问题:
- 不在线程间共享变量
- 将可变的变量改为不可变的变量
- 在访问时使用同步。
有这样一个需求,需要按照序列顺序产生一个序列(不能有重复数据)
自增,看似是一个原子操作实际上一个自增包含三个操作步骤:获取变量值, 将变量值加1,将计算结果写入变量。
/**
*
* @author zhangwei_david
* @version $Id: UnsafeSequence.java, v 0.1 2014年10月24日 下午9:38:54 zhangwei_david Exp $
*/
public class UnsafeSequence {
public static UnsafeSequence unsafeSequence = new UnsafeSequence();
public static UnsafeSequence getInstance() {
return unsafeSequence;
}
private int value;
public int getNext() {
return value++;
}
}
这个类在单线程下是没有任何问题的,可以顺序的生成一个序列。
/**
*
* @author zhangwei_david
* @version $Id: OneThread.java, v 0.1 2014年10月25日 下午9:50:32 zhangwei_david Exp $
*/
public class OneThread {
/**
*
* @param args
*/
public static void main(String[] args) {
for (int i = 0; i < 100; i++) {
System.out.println(UnsafeSequence.getInstance().getNext());
}
}
}
0
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
那么在多项线程下有是什么样的情况呢?
import java.util.concurrent.TimeUnit;
/**
*
* @author zhangwei_david
* @version $Id: Test.java, v 0.1 2014年10月24日 下午9:40:41 zhangwei_david Exp $
*/
public class Test {
/**
*
* @param args
*/
public static void main(String[] args) {
for (int i = 0; i < 100; i++) {
new Thread(new Runnable() {
public void run() {
try {
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
}
System.out.println(UnsafeSequence.getInstance().getNext());
}
}).start();
}
}
}
0
15
18
17
21
24
28
16
14
2
0
1
13
12
5
10
31
33
37
11
6
8
9
7
41
43
40
39
38
36
34
35
45
49
58
32
30
29
27
26
25
23
22
4
20
3
19
88
87
86
85
83
84
80
82
81
79
78
77
76
89
90
74
75
73
72
71
70
94
95
69
67
68
66
65
64
63
97
62
61
59
60
57
56
55
54
53
52
51
50
48
47
46
44
42
98
96
93
92
91
我们可以发现在多线程下,打印的结果是乱序的。 是不是以此就可以断定这个类不是线程安全的内。当然不能,但因的结果是由线程调度和执行的时间决定的。 如果是线程安全的,最终的结果应该自增到99,可是最终没有自增到99,仔细查询结果可以发现有打印了连个零,也就是说有两个线程访问了同一个值,以此可以断定这个类不是线程安全的。
这是由于多线程要共享相同的内存地址空间,并且是并发运行,因此它们可能会访问或修改其他线程正在使用的变量。如果需要是共享的变量的行为可以预测就需要使用同步。如果没有使用同步,那么无论是编译器、硬件还是在运行时都可以对操作进行优化重新排序,这有助有提升性能但也为开发人员带来了负担。
那么如何将这个类改为线程安全的呢? 我们只需将获取下一个值的方法改为同步方法既可以解决这个问题。
/**
*
* @author zhangwei_david
* @version $Id: Sequence.java, v 0.1 2014年10月24日 下午9:51:20 zhangwei_david Exp $
*/
public class SafeSequence {
private static class InstanceHolder {
public static SafeSequence instance = new SafeSequence();
}
public SafeSequence getInstance() {
return InstanceHolder.instance;
}
private volatile int value = 0;
public synchronized int getNext() {
return value++;
}
}