添加Runnable多线程后排序输出首元素重复的代码修改问询
问题分析与解决方案
首先咱们来揪出这个重复元素问题的核心根源:你的主线程在启动排序线程后,根本没等排序线程完成就直接打印列表了!
你看main方法里的代码逻辑:启动name线程后立刻就开始遍历list1输出内容,但nameOrder方法里每次循环都加了Thread.sleep(100),排序过程会变得非常慢。这就导致两个线程同时操作同一个ArrayList——排序线程在一边修改列表元素,主线程在一边读取打印,而ArrayList本身是非线程安全的集合,这种并发读写的场景下,必然会出现数据错乱(也就是你看到的首个元素重复的情况)。
具体问题点拆解
- 线程同步缺失:主线程和排序线程没有同步机制,主线程提前执行打印操作,拿到的是未完成排序且被并发修改的列表状态
- 非线程安全集合:
ArrayList不支持并发的读写/修改操作,多线程下会出现不可预期的行为 - 排序延迟放大问题:
Thread.sleep(100)让排序过程变慢,进一步放大了并发问题的出现概率
修复方案
方案1:让主线程等待排序线程完成(最直接有效)
在启动排序线程后,调用join()方法,让主线程阻塞直到排序线程执行完毕,再执行打印操作:
name.start(); try { name.join(); // 强制主线程等待排序线程执行完成 } catch (InterruptedException e) { e.printStackTrace(); } // 现在打印的就是完全排序后的结果 for (Student i : list1) { System.out.println(i.toString()); }
方案2:改用线程安全集合(可选,需配合同步)
如果确实需要多线程同时操作集合,可以把ArrayList换成线程安全的CopyOnWriteArrayList,不过这个集合更适合读多写少的场景,排序这种频繁修改的场景性能会稍差,但能避免并发修改的异常:
List<Student> list1 = new CopyOnWriteArrayList<Student>();
不过即使换了线程安全集合,还是建议加上join(),否则你打印的可能还是未完成排序的中间状态列表。
额外优化建议
- 如果排序不需要异步执行,其实没必要把排序放到线程里,单线程执行就不会有这些问题
- 你自己实现的冒泡排序可以替换成JDK自带的高效排序方法,配合自定义比较器,既简洁又避免手写排序的bug:
Collections.sort(list1, (s1, s2) -> { int nameCompare = s1.getName().compareToIgnoreCase(s2.getName()); if (nameCompare != 0) { return nameCompare; } // 名称相同时按pointValue降序排列 return Double.compare(s2.pointValue(), s1.pointValue()); });
内容的提问来源于stack exchange,提问作者Samuel Rodriguez
相关产品推荐
相关产品推荐

