并发编程 synchronized (三) 变量是否线程安全

本文通过示例详细分析了Java中的线程安全与不安全情况。线程安全包括单线程操作变量、只读共享变量以及局部变量,而线程不安全主要是多线程对变量的读写操作。同时,文章提醒了子类重写父类方法可能导致的线程安全问题,并强调了变量访问控制的重要性。
摘要由CSDN通过智能技术生成

目录

线程安全情况

单线程操作变量

多个线程对变量只读 或者 变量不属于共享资源

线程不安全情况

多线程对变量既读又写

子类重写父类方法执行未知操作


变量:普通成员变量、静态成员变量、局部变量

① 变量不属于多线程共享资源或者是共享资源不被多线程共享,则线程安全

② 变量被线程共享;但是只有读操作,不包含任何写(修改)操作;则线程安全

线程安全情况

单线程操作变量

@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 修饰

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值