成员变量和静态变量是否线程安全
1.如果没有被共享,则线程安全
2.如果被共享了,根据它们的状态是否能够改变,又分两种情况
2.1如果只有读操作,则线程安全
2.2如果有读写操作,则这段代码是临界区,需要考虑线程安全
这个例子就证明成员变量被共享后线程不安全
ArrayList<String> list = new ArrayList<>();
public void method1(int loopNumber) {
for (int i = 0; i < loopNumber; i++) {
//临界区, 会产生竞态条件
method2();
method3();
//临界区
}
}
private void method2() {
list.add("1");
}
private void method3() {
list.remove(0);
}
//测试
public static void main(String[] args) {
Test11 test = new Test11();
for (int i = 0; i < 2; i++) {
new Thread(() -> {
test.method1(200);
}, "Thread" + i).start();
}
}
运行结果:
Exception in thread "Thread0" java.lang.IndexOutOfBoundsException: Index: 0, Size: 0
at java.util.ArrayList.rangeCheck(ArrayList.java:653)
at java.util.ArrayList.remove(ArrayList.java:492)
at com.concurrent.test4.Test11.method3(Test11.java:40)
at com.concurrent.test4.Test11.method1(Test11.java:32)
at com.concurrent.test4.Test11.lambda$main$0(Test11.java:47)
at com.concurrent.test4.Test11$$Lambda$1/866191240.run(Unknown Source)
at java.lang.Thread.run(Thread.java:745)
上面的例子为什么不是线程安全的呢:主要问题是在于list不是线程安全的,可以这样理解:thread0主存读取当前size 0,add 1 remove 0,但是并未来的及刷新主存,线程B主存读取size 0 add 1 刷新主存,此时线程A也刷新主存,导致最终的size是0.线程B再执行remove操作的时候,有可能先从主存同步数据,拿到size 0。
注意:线程从主存同步数据和修改后将数据同步回去的时机是随机的
成员变量和静态变量是否线程安全?
- 局部变量是线程安全的
- 但局部变量引用的对象则未必
- 如果该对象没有逃离方法的作用访问,它是线程安全的
- 如果该对象逃离方法的作用范围,需要考虑线程安全
将上面的成员变量改为局部变量:
public void method1(int loopNumber) {
for (int i = 0; i < loopNumber; i++) {
ArrayList<String> list = new ArrayList<>();
//临界区, 会产生竞态条件
method2();
method3();
//临界区
}
}
private void method2() {
list.add("1");
}
private void method3() {
list.remove(0);
}
//测试
public static void main(String[] args) {
Test11 test = new Test11();
for (int i = 0; i < 2; i++) {
new Thread(() -> {
test.method1(200);
}, "Thread" + i).start();
}
}
分析:因为list 是局部变量,每个线程调用时会创建其不同实例,没有共享.而 method2 的参数是从 method1 中传递过来的,与 method1 中引用同一个对象
method3 的参数分析与 method2 相同,因此不存在线程安全问题