本文主要是介绍3.3、变量的线程安全分析,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!
变量的线程安全分析
成员变量和静态变量是否线程安全?
- 如果它们没有共享,则线程安全
- 如果它们被共享了,根据它们的状态是否能够改变,又分两种情况
- 如果只有读操作,则线程安全
- 如果有读写操作,则这段代码是临界区,需要考虑线程安全
局部变量是否线程安全
- 局部变量是线程安全的
- 单局部变量引用的对象则未必
- 如果该对象没有逃离方法的作用访问,它是线程安全的
- 如果该对象逃离方法的作用范围,需要考虑线程安全
局部变量线程安全分析
public static void test1() {int i = 10; i++;
}
每个线程调用test1()方法时局部变量i,会在每个线程的栈帧内存中被创建多份,因此不存在共享
public static void test1();descriptor: ()Vflags: ACC_PUBLIC, ACC_STATICCode:stack=1, locals=1, args_size=00: bipush 102: istore_03: iinc 0, 16: returnLineNumberTable:line 10: 0line 11: 3line 12: 6LocalVariableTable:Start Length Slot Name Signature3 4 0 i I
如图
局部变量的引用稍有不同
先看一个成员变量的例子
public class Test2 {public static void main(String[] args) {ThreadUnsafe threadUnsafe = new ThreadUnsafe();for (int i = 0; i < 2; i++) {new Thread(() -> {threadUnsafe.method1(200);}, "t" + i).start();}}
}class ThreadUnsafe {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);}}
其中一种情况是,如果线程2还未add,线程1 remove就会报错:
Exception in thread "t1" java.lang.IndexOutOfBoundsException: Index: 0, Size: 0at java.util.ArrayList.rangeCheck(ArrayList.java:657)at java.util.ArrayList.remove(ArrayList.java:496)at sync.test.ThreadUnsafe.method3(Test2.java:39)at sync.test.ThreadUnsafe.method1(Test2.java:30)at sync.test.Test2.lambda$main$0(Test2.java:15)at java.lang.Thread.run(Thread.java:748)
具体原因是:
new Thread(() -> {list.add("1"); // 时间1. 会让内部 size ++list.remove(0); // 时间3. 再次 remove size-- 出现角标越界
}, "t1").start();new Thread(() -> {list.add("2"); // 时间1(并发发生). 会让内部 size ++,但由于size的操作非原子性, size 本该是2,但结果可能出现1list.remove(0); // 时间2. 第一次 remove 能成功, 这时 size 已经是0
}, "t2").start();
分析:
- 无论哪个线程中的method2引用的都是同一个对象中的list成员变量
- method3与method2分析相同
将list修改为局部变量
public class Test2 {public static void main(String[] args) {ThreadUnsafe threadUnsafe = new ThreadUnsafe();for (int i = 0; i < 2; i++) {new Thread(() -> {threadUnsafe.method1(200);}, "t" + i).start();}}
}class ThreadUnsafe {// ArrayList<String> list = new ArrayList<>();public void method1(int loopNumber) {ArrayList<String> list = new ArrayList<>();for (int i = 0; i < loopNumber; i++) {// 临界区,会产生竞态条件method2(list);method3(list);}}private void method2(ArrayList<String> list) {list.add("1");}private void method3(ArrayList<String> list) {list.remove(0);}}
就不会有上述的问题了
分析
- list是局部变量,每个线程调用时会创建不同的实例,没有共享
- 而method2的参数是从method1中传递过来的,与method1中引用同一个对象
- method3同理
方法访问修饰符带来的思考,如果把method2和method3的方法修改为public会不会有线程安全问题?
- 情况1:有其他线程调用method2和method3
- 情况2:在情况1的基础上,为ThreadSafe类添加子类,子类覆盖method2和method3方法
即:
class ThreadUnsafe {// ArrayList<String> list = new ArrayList<>();public void method1(int loopNumber) {ArrayList<String> list = new ArrayList<>();for (int i = 0; i < loopNumber; i++) {// 临界区,会产生竞态条件method2(list);method3(list);}}public void method2(ArrayList<String> list) {list.add("1");}public void method3(ArrayList<String> list) {list.remove(0);}}class ThreadUnsafeSub extends ThreadUnsafe {@Overridepublic void method3(ArrayList<String> list) {new Thread(() -> {list.remove(0);}).start();}
}
从这个例子可以看出private或final提供【安全】的意义所在,请体会开闭原则中的【闭】
常见的线程安全类
- String
- integer
- StringBuffer
- Random
- Vector
- hashtable
- java.util.concurrent包下的类
这里说他们是线程安全的是指,多个线程调用他们同一个实例的某个方法时,是线程安全的,也可以理解为
public static void main(String[] args) {Hashtable<String, String> hashtable = new Hashtable<>();new Thread(() -> {hashtable.put("1", "1");}).start();new Thread(() -> {hashtable.put("2", "2");}).start();}
- 他们的每个方法是原子的
- 但注意他们多个方法的组合不是原子的
线程安全类方法的组合
分析下面代码是否线程安全?
Hashtable table = new Hashtable();
// 线程1,线程2
if( table.get("key") == null) {table.put("key", value);
}
不可变类线程安全性
String、Integer等都是不可变类,因为其内部的状态不可以改变,因此他们的方法都是线程安全的
String的replace,substring等方法【可以】改变值,那么这些方法是如何保证线程安全的呢?
public class Immutable {private int value = 0;public Immutable(int value) {this.value = value;}public int getValue() {return this.value;}public Immutable add(int v) {return new Immutable(this.value + v);}}
实例分析
暂略
这篇关于3.3、变量的线程安全分析的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!