成员变量和静态变量是否线程安全?
- 如果它们没有被共享,则线程安全
- 如果它们被共享了,根据它们的状态是否能够改变,又分为两种情况
- 如果只有读操作,则线程安全
- 如果有读写操作,则这段代码是临界区,需要考虑线程安全
局部变量是否线程安全?
- 局部变量是线程安全的
- 但局部变量引用的对象则未必
- 如果该对象没有发生逃逸,则他是线程安全的
- 如果该对象发生了逃逸,需要考虑线程安全
public static void test1() {
int i = 10;
i++;
}
每个线程调用 test1() 方法时局部变量 i,会在每个线程的栈帧内存中被创建多份,因此不存在共享
线程操控的 i 变量是不一样的,因此不会存在线程不安全
引用变量的引用稍有不同
执行
static final int THREAD_NUMBER = 2;
static final int LOOP_NUMBER = 200;
public static void main(String[] args) {
ThreadUnsafe test = new ThreadUnsafe();
for (int i = 0; i < THREAD_NUMBER; i++) {
new Thread(() -> {
test.method1(LOOP_NUMBER);
}, "Thread" + i).start();
}
}
method2往list中添加一个数,method3往list移除一个数
多个线程同时操作list,可能会发生线程不安全
俩个线程操作的一个对象
而将类改变为下列情况时
- list 是局部变量,每个线程调用时会创建其不同实例,没有共享
- 而 method2 的参数是从 method1 中传递过来的,与 method1 引用同一个对象
- method3 的参数分析与 method2 相同
此种情况不会发生线程不安全
当我们继承ThreadSafe并重写其中的method3方法时
class ThreadSafe {
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 ThreadSafeSubClass extends ThreadSafe {
@Override
public void method3(ArrayList<String> list) {
new Thread(() -> {
list.remove(0);
}).start();
}
}
static final int THREAD_NUMBER = 2;
static final int LOOP_NUMBER = 200;
public static void main(String[] args) {
ThreadUnsafe test = new ThreadUnsafe();
for (int i = 0; i < THREAD_NUMBER; i++) {
new Thread(() -> {
test.method1(LOOP_NUMBER);
}, "Thread" + i).start();
}
}
再去调用子类的method1方法,就可能出现线程不安全的情况,method3先于method2执行,即新创建的线程先于main线程执行,出现空指针异常的情况。
如果将ThreadSafe进行改写:
class ThreadSafe {
public final 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);
}
}
可以使得ThreadSafe变的安全,即private、final可以让类变的更加安全 --> 线程安全
常见的线程安全类
- String
- Integer等其他包装类
- StringBuffer
- Random
- Vector
- Hashtable
- java.util,concurrent(JUC)包下的类
这里说它们是线程安全的是指,多个线程调用它们同一个实例的某个方法时,是线程安全的。
- 它们的每个方法是原子的
- 但注意它们多个方法的组合不是原子的
线程安全类方法的组合
HashTable仅仅可以保证get方法是线程安全的,也能保证put方法是线程安全的,但是不能保证两个方法的组合是安全的。
不可变类线程安全性
String、Integer 等都是不可变类,因为其内部的状态不可以改变,因此它们的方法都是线程安全的。
HashMap不是线程安全的,HashTable是
String是不可变类,线程安全
也是线程安全的
Date也是线程不安全的
加了final仅仅是不能改变指向,而被指向的地址的内容是可以被改变的,因此同样线程不安全
多个线程可以调用update()方法,修改count变量,因此是线程不安全的
默认是单例的,即start是共享变量,显然线程不安全
可以做成@Arround环绕通知,然后将start设置为局部变量
从下向上看,UserDaoImpl线程是否安全
线程安全,没有成员变量,conn是局部变量,并且没有发生逃逸。
UserServiceImpl虽然userDao是共享变量,但是userDao没有成员变量,也不会发生线程不安全的情况。
最上层方法,同UserServiceImpl,线程安全。
例5仅仅改变 UserDaoImpl 类
conn没有作为方法内的局部变量,而是作为成员变量出现,这样就可能导致线程不安全的问题出现。
例6在例子5的基础上将 UserSerivceImpl 方法修改,将userDao变为方法内的局部变量,这样做UserDaoImpl虽然是线程不安全的,但是 UserSerivceImpl 是线程安全的
其中foo的行为是不确定的,可能导致不安全的发生,称之为外星方法