写点什么

并发中 atomic BUG 分享

作者:FunTester
  • 2023-08-07
    北京
  • 本文字数:1526 字

    阅读完需:约 5 分钟

在使用 Java 做性能测试的过程中,遇到过很多自己抗自己的坎儿。在经历过风风雨雨之后,自认为已经是个并发编程的老司机,没想到前两天又丢进了同一个坑中。


保持操作的原子性!!!保持操作的原子性!!!保持操作的原子性!!!


重要的事情写三遍。


事情是这样,要写一个脚本,需求是对所有的用户进行初始化(包含 HTTP 请求),为了加速,自然选择了并发实现。(隐藏需求:每个用户都需要初始化,但可以重复初始化)


为了达到动态调整初始化的 QPS,选择了动态 QPS 实现这个功能。


下面是伪代码的实现:


package com.funtest.temp

class AtomicTest {
static List<User> users
static class User {
String token
int uid

void init() { //用户初始化 } }
}
复制代码


初步的思路是使用之前储备的顺序(伪随机)方法:


    /**     * 随机选择某个对象     *     * @param list     * @param index 自增索引     * @param <F>     * @return     */    public static <F> F random(List<F> list, AtomicInteger index) {        if (list == null || list.isEmpty()) ParamException.fail("数组不能为空!");        return list.get(index.getAndIncrement() % list.size());    }
复制代码


然后通线程安全的随机方法,实现每次请求的时候都使用不同的用户。然后通过识别index的实际地增值来判断是否完成所有用户的遍历。


    static void main(String[] args) {        AtomicInteger index = new AtomicInteger()        def test = {            SourceCode.random(users, index).init()            if (index.get() > users.size()) FunQpsConcurrent.stop()        }        new FunQpsConcurrent(test, "原子操作BUG演示").start()    }
复制代码


但是 BUG 就来了,当我在测试的发现static List<User> users最后的几个用户始终无法完成用户的初始化。后来经过简单排查,发现了自己的问题。


用了线程安全类,并不是就安全


因为线程安全类在原子操作中是安全的,我下面吧test{}里面的重新分享一下。


        def test = {       1.   def random = SourceCode.random(users, index)       2.   random.init()       3.   if (index.get() > users.size()) FunQpsConcurrent.stop()        }
复制代码


当第 1 行执行完,index已经递增了。但是其他线程执行到第 3 行,拿到已经递增的index值,然后判断执行了退出程序。


处理方法 3 种:


  1. 优化stop()方法,因为一旦index值到达阈值,就强制终止了执行。实际应该等到test{}全部执行完再执行推出程序。

  2. 额外增加一个java.util.concurrent.atomic.AtomicInteger对象,用来统计执行完成的用户数,而不是随机获取到用户数量。

  3. 使用线程安全的集合类对象,例如java.util.concurrent.LinkedBlockingQueue,然后poll()为空再执行退出方法。


今天的分享就到这里,其实 Java 在做性能测试方面还是简单的,多使用多踩坑,很容易掌握的。

知识补充:


在计算机科学中,"atomic"(原子)是指一种不可再分割的操作单位。原子操作是指一个操作或一组操作在执行过程中不会被中断,要么全部执行完成,要么完全不执行,没有中间状态。这样的操作能够确保在多线程或并发环境下,操作的执行是线程安全的,不会发生竞态条件或数据不一致的问题。


原子操作通常用于对共享资源进行修改或访问的场景,比如多线程修改同一个变量、更新共享数据结构等。它可以保证操作的完整性,不受其他线程的干扰,从而避免并发冲突。


在编程中,原子操作通常由特定的原子指令或者使用锁(如互斥锁或读写锁)来实现。在现代编程语言中,也提供了原子操作的支持,比如 Java 中的AtomicInteger等,这些特殊的数据结构或函数允许开发者进行原子操作,确保线程安全性。

发布于: 刚刚阅读数: 3
用户头像

FunTester

关注

公众号:FunTester,800篇原创,欢迎关注 2020-10-20 加入

Fun·BUG挖掘机·性能征服者·头顶锅盖·Tester

评论

发布
暂无评论
并发中atomic BUG分享_FunTester_InfoQ写作社区