Android HashSet多线程添加User对象去重失效问题咨询
问题现象
发起网络请求并完成数据库数据校验后,Set集合无法正常过滤重复数据。
具体场景:发起网络请求拉取全部帖子数据后,计划将用户数据存入Set以实现天然去重,但实际运行时重复用户也会被成功添加,总计10条包含重复条目的用户数据全部打印了"ok user add"的添加成功日志。
现有实现代码
网络请求核心逻辑
public static void send_request_post(String sid, int did, Activity activity, AppCompatActivity appCompatActivity) { RequestQueue requestQueue = Volley.newRequestQueue(activity); JSONObject requestParam = new JSONObject(); String GET_POST = "https://helme/cc/helpme.php"; Set<User> user= new HashSet<User>(); Log.d("main_activity", "fino a qui funziona"); try { requestParam.put("sid", sid); requestParam.put("did", did); } catch (JSONException e) { e.printStackTrace(); } JsonObjectRequest req = new JsonObjectRequest( Request.Method.POST, GET_POST, requestParam, new Response.Listener<JSONObject>() { @Override public void onResponse(JSONObject response) { try { JSONArray sid_user111 = response.getJSONArray("posts"); for (int j = 0; j < sid_user111.length(); j++) { JSONObject postss = sid_user111.getJSONObject(j); new Thread(new Runnable() { @Override public void run() { try { if(!(Model_user.getInstance (activity.getApplicationContext()).getcontainsuid (postss.getString("author")))){ user.add(new User(postss.getString("author"), postss.getString("authorName"), String.valueOf(0), postss.getString("pversion"))); Log.d("main_activity_user","ok user add"); }} catch (JSONException e) { e.printStackTrace(); } } }).start(); } } catch (JSONException e) { e.printStackTrace(); } } }, new Response.ErrorListener() { @Override public void onErrorResponse(VolleyError error) { } }); MainActivity.requestQueue.add(req); }
User DAO 校验方法
@Query("select Exists(select uid from User where uid =:useruid)") Boolean loadAllByuid(String useruid);
该方法用于校验指定作者uid是否已存在于本地数据库中。
Model_user 单例实现
public class Model_user { private static Model_user theinstance = null; private static AppDatabase userDAOs = null; public static synchronized Model_user getInstance() { if (theinstance == null) { theinstance = new Model_user(); } return theinstance; } public static synchronized Model_user getInstance(Context c) { if (theinstance == null) { userDAOs = Room.databaseBuilder(c, AppDatabase.class, "user_db").build(); theinstance = new Model_user(); } return theinstance; } }
问题根因
重复数据被成功添加由三个核心问题共同导致:
- User类未重写
equals()和hashCode()方法HashSet去重的判断逻辑是先比对对象的hashCode值,hashCode一致时再调用equals()确认对象是否相等。如果自定义User类没有重写这两个方法,会默认继承Object类的实现,仅通过对象内存地址判定是否相等。哪怕两个User实例的uid、用户名等属性完全一致,只要是不同的new出来的对象,就会被判定为不同元素,直接加入Set。 - 多线程并发操作非线程安全集合
HashSet本身是线程不安全的容器,代码中在循环内为每一条帖子都新建了独立线程,并发操作同一个Set实例。多线程同时调用add()方法时,没有加锁也没有使用线程安全的集合实现,会出现写入覆盖、判断逻辑失效的问题,直接导致去重逻辑完全不可靠。 - 数据库校验存在竞态条件
子线程的执行顺序是不可控的,所有线程几乎同时启动:如果两条同作者的帖子对应的查库请求同时执行,此时数据库中还没有该用户的记录,两个线程都会拿到"用户不存在"的校验结果,后续都会执行add操作,最终导致重复数据写入。
修复方向
- 为User类重写
equals()和hashCode()方法,以唯一标识用户的uid(即author字段)作为相等判定的核心依据。 - 避免在循环中随意创建无管控的子线程,要么在当前回调线程顺序遍历处理数据,要么使用线程池统一调度任务;同时将
HashSet替换为线程安全的实现,比如用Collections.synchronizedSet()包装Set,或直接使用ConcurrentHashMap.newKeySet()。 - 调整去重流程:先遍历所有帖子数据,在内存层按uid做第一轮去重,再和数据库做批量校验,最后将不存在的用户批量入库、存入Set,从流程上避免多线程竞态问题。
内容的提问来源于stack exchange,提问作者Elly
相关产品推荐
相关产品推荐

