目录
变量:普通成员变量、静态成员变量、局部变量
① 变量不属于多线程共享资源或者是共享资源不被多线程共享,则线程安全
② 变量被线程共享;但是只有读操作,不包含任何写(修改)操作;则线程安全
线程安全情况
单线程操作变量
@Slf4j(topic = "c.SafeThread")
public class SafeThread {
private List<Integer> list1 = new ArrayList(); // 普通成员变量
private static List<Integer> list2 = new ArrayList(); // 静态成员变量
private final int OP_NUM = 1000; // 操作次数
private final void method(){
List<Integer> list3 = new ArrayList(); // 局部变量
for(int i = 0; i < OP_NUM; i++){
list1.add(i); // 普通成员变量添加一个元素
list2.add(i); // 静态成员变量添加一个元素
list3.add(i); // 局部变量添加一个元素
list1.remove(0); // 普通成员变量移除一个元素
list2.remove(0); // 静态成员变量移除一个元素
list3.remove(0); // 局部变量移除一个元素
}
log.debug("最终 普通成员变量list1 容量 {}", list1.size());
log.debug("最终 静态成员变量list2 容量 {}", list2.size());
log.debug("最终 局部变量list3 容量 {}", list3.size());
}
public static void main(String[] args) {
SafeThread st = new SafeThread();
st.method();
}
}
list1 普通成员变量,对于当前对象是共享资源
list2 静态成员变量,对于当前类是共享资源
list3 局部变量,在方法 method 中是共享资源
但是它们都只在一个线程中(main 线程)被使用,没有被多线程共享,所以线程安全
多个线程对变量只读 或者 变量不属于共享资源
@Slf4j(topic = "c.SafeThread0")
class SafeThread0{
private static final int THREAD_NUM = 4; // 线程数量
private List<Integer> list1 = new ArrayList(); // 普通成员变量
private static List<Integer> list2 = new ArrayList(); // 静态成员变量
public SafeThread0(){
list1.add(1); // 给 list1 添加元素
list2.add(2); // 给 list2 添加元素
}
private void method(List<Integer> list3){
log.debug("获取 普通成员变量 list1 第一个元素 {}", list1.get(0));
log.debug("获取 静态成员变量 list2 第一个元素 {}", list2.get(0));
log.debug("移除 局部变量 list3 第一个元素 {}", list3.remove(0)); // 此处有写操作,但还是线程安全的
}
private void method0(){
List<Integer> list3 = new ArrayList();
list3.add(3);
method(list3);
}
public static void main(String[] args) {
SafeThread0 sf = new SafeThread0();
for(int i = 0; i < THREAD_NUM; i++){
new Thread(() -> {
sf.method0();
}, "t" + i).start();
}
}
}
程序正常结束
① list1、list2 对于线程 t0 ~ t3 属于共享资源,但是只有读操作,线程安全
② list3 在线程 t0 ~ t3 调用方法 method 时,都会创建新的,不属于共享变量,线程安全
线程不安全情况
多线程对变量既读又写
@Slf4j(topic = "c.UnsafeThread")
public class UnsafeThread {
private final int THREAD_NUM = 10;
private final int OP_NUM = 1000;
private List<Integer> list = new ArrayList();
private void add(){
list.add(1);
}
private void remove(){
list.remove(0);
}
private void method(){
for(int i = 0; i < THREAD_NUM; i++){
new Thread(() -> {
for(int j = 0; j < OP_NUM; j++){
add();
remove();
}
}, "t" + i).start();
}
}
public static void main(String[] args) {
new UnsafeThread().method();
}
}
在线程数量少、执行次数少时,可能不会抛出异常;但是这些都是偶然的,多线程对变量既读又写是线程不安全的
子类重写父类方法执行未知操作
class SafeBefore{
public void method(List<Integer> list){
add(list);
remove(list);
}
private void add(List<Integer> list){
list.add(1);
}
public void remove(List<Integer> list){ // 父类暴露了内部方法
list.remove(0);
}
}
class UnsafeAfter0 extends SafeBefore{
@Override
public void remove(List<Integer> list) { // 字类重写父类方法
list.remove(0);
list.remove(0);
}
}
class Test8{
public static void main(String[] args) {
SafeBefore uf = new UnsafeAfter0();
List<Integer> list = new ArrayList();
uf.method(list);
}
}
class UnsafeAfter1 extends SafeBefore{
@Override
public void remove(List<Integer> list) { // 字类重写父类方法
new Thread(() -> { // 直接创建出一个新线程,使得 list 变成共享资源
list.set(1, 1);
}, "Unsafe1").start();
}
}
class Test8{
public static void main(String[] args) {
SafeBefore uf = new UnsafeAfter1();
List<Integer> list = new ArrayList();
uf.method(list);
}
}
① 虽然只有 main 一个线程,但是子类重写父类方法,一顿乱操作;直接导致线程不安全
② 直接创建了一个新线程 "Unsafe1" 使得 list 变成共享资源,新线程还对 list 进行写操作
一个类中方法,涉及到具体实现或者线程安全问题;最好不要外露,用 private final 修饰