一、变量的线程安全分析
成员变量和静态变量是否线程安全?
- 如果它们没有共享,则线程安全
- 如果它们被共享了,根据它们的状态是否能够改变,又分两种情况:
如果只有读操作,则线程安全
如果有读写操作,则这段代码是临界区,需要考虑线程安全
局部变量是否线程安全?
- 局部变量是线程安全的
- 但局部变量引用的对象则未必
如果该对象没有逃离方法的作用访问,它是线程安全的
如果该对象逃离(return)方法的作用范围,需要考虑线程安全
1.1 局部变量线程安全分析:
如下所示,每个线程调用 test1() 方法时局部变量 i,会在每个线程的栈帧内存中被创建多份,因此不存在共享。
public static void test1() {
int i = 10;
i++;
}
如下图所示,线程0和线程1都调用了test1()方法,他们都会创建一个test1的栈帧,这两个存在于各自线程内部的栈帧互不影响,局部变量i也是栈帧中私有的内存变量值,不会互相影响。
1.2 局部变量引用
先看一个成员变量的例子:
public class App
{
static final int THREAD_NUMBER = 2;
static final int LOOP_NUMBER = 200;
public static void main( String[] args ) throws InterruptedException {
ThreadUnsafe test = new ThreadUnsafe();
for(int i=0;i<THREAD_NUMBER;i++){
//创建两个线程,对test对象执行相应的操作
new Thread(()->{
test.method1(LOOP_NUMBER);
},"Thread"+(i+1)).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);//从集合中移除元素
}
}
以上代码创建两个线程,同时对ThreadUnsafe对象中的ArrayList<String>集合执行添加以及删除元素操作。该代码可能会由于线程2还未add元素,线程1remove元素而报错。
分析:
多线程对共享资源有修改操作,产生临界区代码,会有线程安全的问题。
- 无论哪个线程中的method2()和method3()方法引用的都是同一个对象中的list成员变量(共享资源)
与成员变量对比的局部变量示例(局部变量没有暴漏给外部):
public class App
{
static final int THREAD_NUMBER = 2;
static final int LOOP_NUMBER = 200;
public static void main( String[] args ) throws InterruptedException {
ThreadSafe test = new ThreadSafe();
for(int i=0;i<THREAD_NUMBER;i++){
//创建两个线程,对test对象执行相应的操作
new Thread(()->{
test.method1(LOOP_NUMBER);
},"Thread"+(i+1)).start();
}
}
}
class ThreadSafe{
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 相同
二、常见线程安全类
2.1 常见线程安全类:
• String
• Integer 包装类
• StringBuffer
• Random
• Vector
• Hashtable
• java.util.concurrent包下的类
此处说的线程安全是指,多个线程调用线程安全类的同一个实例的某个方法时,是线程安全的。可以理解为:
- 他们的每个方法是原子的
- 但是注意:他们多个方法的组合不是原子的,如:
Hashtable table = new Hashtable();
// 线程1,线程2
if( table.get("key") == null) {
table.put("key", value);
}
上例中,类Hashtable类是线程安全的类,因此其每个方法都是原子的,即table.get()方法和table.put()方法单个看都是线程安全的。 但是,当两个方法组合在一起时,如两个线程1,2都执行上述代码,则不是线程安全的,如下图所示:
2.2 不可变类线程安全性
String、Integer 等都是不可变类,因为其内部的状态不可以改变(只能读,不能改写),因此它们的方法都是线程安全的。
三、结束
本文主要学习了变量的线程安全,其主要落脚点还是看是否共享资源并读写。