一、先说结论
1.1 成员变量和静态变量是否线程安全?
1、如果它们没有被共享,则线程安全。
2、如果它们被共享了,根据它们的状态是否能够改变,又分两种情况:
(1)如果只有读操作,则线程安全
(2) 如果有读写操作,需要考虑线程安全问题(读时可能会读到中间结果,所以有读写时,读也要考虑线程安全)
1.2 局部变量是否线程安全?
1、基本数据类型的局部变量是线程安全的。
2、引用类型的局部变量,要看该对象有没有逃离方法的作用范围,
(1)如果对象仅在方法内创建、使用、消亡,则是线程安全的;
(2)如果一个对象由外部传入,或者传出外部,则需要考虑线程安全问题(外部仅读,线程安全;外部有读写--如果不考虑同步机制的话,会存在线程安全问题)
举例见本文第三章--举例1
二、常见的线程安全类
- String
- 包裹类,如Integer
- StringBuffer
- Random
- Vector
- Hashtable
- java.util.concurrent 包下的类
线程安全类的方法是线程安全的(final修饰的类为不可变类,不可变类的方法也是线程安全的)
线程安全类的单个方法是线程安全的,但是多个方法的组合不一定是线程安全的。
三、举例分析(重点)
3.1 举例1
package com.fuping3.safe;
public class StringBuilderTest {
int num = 10;
//s1的声明方式是线程安全的(只在方法内部用了)
public static void method1(){
//StringBuilder类型本身是线程不安全
StringBuilder s1 = new StringBuilder();
s1.append("a");
s1.append("b");
//...
}
//sBuilder的操作过程:是线程不安全的(作为参数传进来,可能被其它线程操作)
public static void method2(StringBuilder sBuilder){
sBuilder.append("a");
sBuilder.append("b");
//...
}
//s1的操作:是线程不安全的(有返回值,可能被其它线程操作)
public static StringBuilder method3(){
StringBuilder s1 = new StringBuilder();
s1.append("a");
s1.append("b");
return s1;
}
//s1的操作:是线程安全的(s1自己消亡了,最后返回的s1.toString是新建的的一个对象)
/*
*StringBuilder类中toString源码
* public String toString() {
* return new String(this.value, 0, this.count);
* }
*/
public static String method4(){
StringBuilder s1 = new StringBuilder();
s1.append("a");
s1.append("b");
return s1.toString();
}
}
3.2 举例2:
/**
*前置知识--HttpServlet默认是单例的
*/
public class MyServlet extends HttpServlet{
Map<String,Object> map=new HashMap<>(); //线程不安全
final String S2="XXX"; //线程安全,因为final修饰的String类型,值不可被改变
Date D1=new Date(); //线程不安全
final Date D2=new Date(); //线程不安全,因为虽然是被final修饰的常量,但仅是地址不可变,值仍可变
}
3.3 举例3:
分析:
1、MyServlet 继承自HttpServlet是单例的,多线程调用时,userService对象只有一个。
2、userService中的count成员变量,由于被共享读写,所以是线程不安全的
3.4 举例4
分析:
1、MyAspect类的@Scope默认没写,所以是单例的。
2、start成员变量,由于被共享读写,所以是线程不安全的
3.5 举例5
分析:
1、MyServlet 继承自HttpServlet是单例的,多线程调用时,userService对象只有一个,也为单例。
2、userService对象只有一个,所以userDao对象也只有一个。
3、由于UserDaoImpl--update方法中局部变量sql、conn作用范围都仅在update方法中,所以update方法是线程安全的,所以整体是线程安全的
3.6 举例6:
分析:
1、MyServlet 继承自HttpServlet是单例的,多线程调用时,userService对象只有一个,也为单例。
2、userService对象只有一个,所以userDao对象也只有一个。
3、由于UserDaoImpl的成员变量conn,在多线程环境下会被共享读写,所以整体是线程不安全的。
3.7 举例7
springMVC三层结构中线程安全性写法:
1、每次调用,都新建一个service层对象,或dao层对象,从而不共享成员变量或类变量;
或
2、变量作用范围都在方法内部;
或
3、只读共享变量
分析:
1、MyServlet 继承自HttpServlet是单例的,多线程调用时,userService对象只有一个,也为单例。
2、userService对象只有一个,但是userServiceImpl--update方法每次被调用时,都会new一个userDao对象,所以以userDao对象是多个。
3、UserDaoImpl的成员变量conn,虽然在UserDaoImpl--update方法中会被修改,但是conn并未被共享(userDao对象是多个),所以整体是线程安全的。