回顾
多个线程对共享资源进行写操作时,会发生上下文切换,造成线程安全问题。
一段代码块中如果存在对共享资源的多线程读写操作,则称这段代码区为临界区。
多个线程在临界区内执行,代码执行序列不同导致结果无法预测,称为发生了竞态条件。
变量的线程安全分析
1.成员变量和静态变量是否线程安全?
只有当它们被共享,并且不只有读,还有写操作时,会发生临界区的竞态条件,需要考虑线程安全问题
2.局部变量是否线程安全?
局部变量是线程安全的
局部变量引用的对象未必,如果该对象逃离方法的作用范围,则需要考虑线程安全问题
局部变量线程安全分析
每个线程调用某一方法中的局部变量时,会在每个线程的栈帧内存中创建多份,变量并不共享
如图
局部变量引用对象如果是共享的,可能发生线程安全问题,例子如下
将list改为局部变量,list就不是共享资源了,线程就是安全的了
上述代码中将private改为了public
这时如果有其他线程调用method2和method3也不会发生安全问题
但是如果为这个类添加子类,子类覆盖method2或method3方法,即
新线程和原来的线程有共享资源,就有了线程安全问题
因此可以用private修饰method2和method3,并且用final修饰method1来防止子类重写方法,发生线程安全问题
可见private和final可以提供安全,这也是开闭原则中的闭
常见的线程安全类
String,Integer,StringBuffer,Random,Vector,Hashtable,java.util.concurrent包下的类(JUC)多个线程调用它们同一个实例的方法时是线程安全的。
它们的每个方法是原子的(如Hashtable中的put方法是用synchronzied来修饰的),但它们的多
个方法的组合不是原子的。例:
不可变类线程安全性
String,Integer等都是不可变类,其内部状态不可改变(如String的substring方法会复制原有字符串来创建一个新字符串,并没有改变其属性)
下图中D2也不是线程安全的,D2的引用对象不可变,但Date内属性可以变
以下代码也不是线程安全的,update方法内为临界区
以下代码也不是线程安全的,这是单例,start需要被共享,进行读写操作时会发生线程安全问题,可以把使用环绕通知把start变为局部变量
以下代码是线程安全的(推荐使用这种方法),没有成员变量一般都是安全的,如果把conn变为成员变量就会发生线程安全问题
以下代码也是线程安全的
以下代码不是线程安全的,foo的行为不确定,可能导致不安全的发生,称为外星方法
注意闭合原则,String类设为final,防止了子类重写String类中方法,破坏不可变性