成员变量和静态变量是否线程安全?
- 如果它们没有共享,则线程安全。
- 如果它们被共享了,根据他们的状态是否能够改变,又分两种情况
- 只是读操作,则线程安全
- 如果有读写操作,则这段代码是临界区,需要考虑线程安全
局部变量是否线程安全?
局部变量是线程安全的
但局部变量引用的对象则未必
如果该对象没有逃离方法的作用范围,它是线程安全的
如果该对象逃离方法的作用范围,需要考虑线程安全
局部变量线程安全分析
想一想,这段代码被多个线程访问,它是线程安全的吗?
public static void test1() {
int i = 10;
i++;
}
i++不是一个原子操作,它是线程不安全的,也许会从这方面去考虑,但实际上这段代码是线程安全的。
反编译为字节码后,一共有4行指令,第一行指令准备了一个常量10 第二行指令把10赋值给i,第三行指令的时候在局部变量i上进行自增1的操作,第四行指令就return了。
从这几行代码来看, 发现局部变量的i++操作和静态变量的i++的字节码指令是稍有不同的。incc这个指令只有一行就完成了++操作,就不会像静态变量的i++一样指令被分成了多个,影响原子性。
而每个线程调用此方法时,局部变量i会在每个线程的栈帧内存中被创建多份,因此不存在共享。
还记得局部变量是线程私有的吗?
线程分别在自己的栈帧中完成了i++操作,互不影响。
局部变量引用
在循环里调用method1,method2,method3,让mothod2在list里加一个元素,再让method3去remove这个元素。
调用执行后很快的就报错了。
public class TestThreadSafe {
static final int THREAD_NUMBER = 2;
static final int LOOP_NUMBER = 200;
public static void main(String[] args) {
ThreadSafeSubClass test = new ThreadSafeSubClass();
for (int i = 0; i < THREAD_NUMBER; i++) {
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);
}
}
}
因为method2和methoed3都调用的同一个对象中的list成员变量。
进行一些小改动,让它变得线程安全,让list成为方法内的局部变量。
class ThreadSafe {
public final 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");
}
private void method3(ArrayList<String> list) {
System.out.println(1);
list.remove(0);
}
}
分析这段代码为什么变成了线程安全的?
- list是局部变量,每个线程调用时会创建其不同的实例,没有共享。
- 而method2的参数是method1中传递过来的,与method1引用同一个对象。
- method3的参数分析与method2相同。