【多线程: 变量的线程安全分析】
01.介绍
成员变量和静态变量是否线程安全?
如果它们没有共享,则线程安全如果它们被共享了,根据它们的状态是否能够改变,又分两种情况 如果只有读操作,则线程安全 如果有读写操作,则这段代码是临界区,需要考虑线程安全
局部变量是否线程安全?
局部变量是线程安全的 但局部变量引用的对象则未必 如果该对象没有逃离方法的作用访问,它是线程安全的 如果该对象逃离方法的作用范围,需要考虑线程安全
02.成员变量线程安全分析
例子
import java.util.ArrayList;
class ThreadUnsafe {
ArrayList<String> list = new ArrayList<>();
public void method1(int loopNumber) {
for (int i = 0; i < loopNumber; i++) {
// { 临界区, 会产生竞态条件
method2();
method3();
// } 临界区
}
}
public void method2() {
list.add("1");
}
public void method3() {
list.remove(0);
}
}
public class TestXCAQ {
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调用了method2与method3 题目分别为添加一个字符串和删除一个字符串 是读写操作 所以出现了线程安全的问题,即还没有添加就先删除了。
图片解释
03.局部变量线程安全分析
例子
import java.util.ArrayList;
class ThreadUnsafe {
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);
}
}
public class TestXCAQ {
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();
}
}
}
结果
结果为空
解释
我们这里把==list==有成员变量改为了局部变量 可以发现现在的结果为空 说明没有问题,原因是:局部变量每次创建都会复制出一个副本也就是线程1的list与线程2的list不是一个对象,所以不会出现共享同一个对象的问题 所以线程安全。
图片解释
局部变量线程不安全的情况
例子
import java.util.ArrayList;
class ThreadUnsafe {
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 ThreadA extends ThreadUnsafe{
@Override
public void method2(ArrayList<String> list) {
new Thread(()->{
list.add("1");
});
}
}
public class TestXCAQ {
static final int THREAD_NUMBER = 2;
static final int LOOP_NUMBER = 200;
public static void main(String[] args) {
ThreadA test = new ThreadA();
for (int i = 0; i < THREAD_NUMBER; i++) {
new Thread(() -> {
test.method1(LOOP_NUMBER);
}, "Thread" + i).start();
}
}
}
结果
报错
解释
我们创建了ThreadUnsafe的子类ThreadA 并且重写了Thread2方法 把方法改为创建一个线程 此线程负责add,这样我们看结果 发现出现了错误,原因是 此时method2方法里的线程 与 调用method1的线程 现在共享一个对象list 导致线程不安全。
如何解决
1.使用final修饰父类 使其不能被继承2.使用final修饰父类的方法 使其不能被子类重写
3.使用private修饰父类方法 使其不能被子类重写