• 7.使用多线程时,要注意哪些场景?经验之谈


    1.串行操作

    如果我们操作的正确性是依赖时序的,而在多线程的情况下又不能保障执行的顺序和我们预想的一致,这个时候就会发生线程安全问题。
    比如:

    if (map.containsKey(key)) {
        map.remove(obj)
    }
    
    • 1
    • 2
    • 3

    代码中首先检查 map 中有没有 key 对应的元素,如果有则继续执行 remove 操作。

    这个操作其实会出现问题,因为它是先检查后操作,而执行过程中可能会被打断。

    如果此时有两个线程同时进入 if() 语句,然后它们都检查到存在 key 对应的元素,于是都希望执行下面的 remove 操作,随后一个线程率先把 obj 给删除了,而另外一个线程它刚已经检查过存在 key 对应的元素,if 条件成立,所以它也会继续执行删除 obj 的操作,但实际上,集合中的 obj 已经被前面的线程删除了,这种情况下就可能导致线程安全问题

    在我们开发过程中其实会出现很多这种类似的问题,“检查与执行”并非原子性操作,在中间可能被打断,而检查之后的结果也可能在执行时已经过期、无效,换句话说,获得正确结果取决于幸运的时序。

    这种情况下,我们就需要对它进行加锁等保护措施来保障操作的原子性。

    2.共享变量或资源被多线程访问

    典型的场景有访问共享对象的属性,访问 static 静态变量,访问共享的缓存,等等。

    因为这些信息不仅会被一个线程访问到,还有可能被多个线程同时访问,那么就有可能在并发读写的情况下发生线程安全问题。

    比如:

    /**
     * 描述:共享的变量或资源带来的线程安全问题
     */
    public class ThreadNotSafe1 {
    
        static int i;
    
        public static void main(String[] args) throws InterruptedException {
            Runnable r = new Runnable() {
                @Override
                public void run() {
                    for (int j = 0; j < 10000; j++) {
                        i++;
                    }
                }
            };
            Thread thread1 = new Thread(r);
            Thread thread2 = new Thread(r);
            thread1.start();
            thread2.start();
            thread1.join();
            thread2.join();
            System.out.println(i);
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25

    上面意思时两个线程同时对 i 进行 i++ 操作,最后的输出可能是 15885 等小于20000的数,而不是我们期待的20000,这便是非常典型的共享变量带来的线程安全问题。

    3.使用非标明线程安全的类

    我们使用其他类时,如果对方没有声明自己是线程安全的,那么这种情况下对其他类进行多线程的并发操作,就有可能会发生线程安全问题。

    举个例子,比如说我们定义了 ArrayList,它本身并不是线程安全的,如果此时多个线程同时对 ArrayList 进行并发读/写,那么就有可能会产生线程安全问题,造成数据出错,而这个责任并不在 ArrayList,因为它本身并不是并发安全的。

    所以我们把 ArrayList 用在了多线程的场景,需要在外部手动用 synchronized 等方式保证并发安全。

    ArrayList 默认不适合并发读写,是我们错误地使用了它,导致了线程安全问题。

    线程非安全类:

    • StringBuilder
    • ArrayList
    • HashMap
    • HashSet
  • 相关阅读:
    【图片+代码】:GCC 链接过程中的【重定位】过程分析
    基于JAVA外贸服装订单管理系统计算机毕业设计源码+数据库+lw文档+系统+部署
    一款可以自动写代码的编辑器,解放你的双手
    亚马逊AI编程助手CodeWhisperer
    Java泛型总结
    加粗,倾斜,删除线,下划线
    关于App质量把控,我的复盘与思考(上)
    1、Spring IOC的理解要点
    【3D建模全流程教学】在 ZBrush、Maya 和 Marvelous Designer 中制作一位逼真的女孩
    Flink之DataStream API开发Flink程序过程与Flink常见数据类型
  • 原文地址:https://blog.csdn.net/daohangtaiqian/article/details/128144933