Collections.sort 引起的线程不安全操作

测试场景

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
26
27
28
29
30
31
public static void main(String [] args) throws Exception {

//快递优先级对于所有线程来说是一个全局变量
List<String> list = new ArrayList<>();

list.add("A");
list.add("D");
list.add("C");
list.add("B");
list.add("F");
list.add("E");

Runnable runnable = new Runnable() {
@Override
public void run() {
//这里对一个全局变量进行排序
Collections.sort(list);
}
};

ExecutorService executorService = Executors.newFixedThreadPool(10);
//使用1000个线程模拟
for (int i = 0; i < 1000; i++) {
executorService.execute(runnable);
}
try {
TimeUnit.SECONDS.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
}

报错信息

1
2
3
4
5
6
7
Exception in thread "pool-1-thread-2" java.util.ConcurrentModificationException
at java.base/java.util.ArrayList.sort(ArrayList.java:1752)
at java.base/java.util.Collections.sort(Collections.java:145)
at com.example.surf.content.Demo2$1.run(Demo2.java:34)
at java.base/java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1128)
at java.base/java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:628)
at java.base/java.lang.Thread.run(Thread.java:834)

原因

1
2
3
4
5
6
7
public void sort(Comparator<? super E> c) {
final int expectedModCount = modCount;
Arrays.sort((E[]) elementData, 0, size, c);
if (modCount != expectedModCount)
throw new ConcurrentModificationException();
modCount++;
}

在这段源码种,多个线程同时对同一个 list 排序操作,同时修改list,抛出异常

实际场景

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
private Config getConfigByRandomId(String region, Integer randomId) {
List<Config> configs = configService.getConfigMap()
.getOrDefault(region, configService.getConfigMap().get(DEFAULT_REGION));
log.info("sort:{}", JSON.toJSONString(configs));
Collections.sort(configs, Comparator.comparing(Config::getRatio));

int index = 0;
for (Config config : configs) {
index += config.getRatio();
if (index >= randomId) {
return config;
}
}
return configs.get(0);
}

1
2
3
4
5
6
7
2021-07-07 04:38:13.975 ERROR 1 --- [nio-8080-exec-1] o.a.c.c.C.[.[.[/].[dispatcherServlet]    : Servlet.service() for servlet [dispatcherServlet] in context with path [] threw exception [Request processing failed; nested exception is java.util.ConcurrentModificationException] with root cause
java.util.ConcurrentModificationException: null
at java.base/java.util.ArrayList.sort(ArrayList.java:1751) ~[na:na]
at java.base/java.util.Collections.sort(Collections.java:177) ~[na:na]
at com.example.surf.content.controller.ContentController.getConfigByRandomId(ContentController.java:163) ~[classes!/:0.0.1-SNAPSHOT]
at com.example.surf.content.controller.ContentController.layer(ContentController.java:77) ~[classes!/:0.0.1-SNAPSHOT]
at jdk.internal.reflect.GeneratedMethodAccessor132.invoke(Unknown Source) ~[na:na]

这段代码同时从一个 cache 中获取list,然后多个线程对其进行排序。之前一直以为是缓存失效导致数据被删除,找错方向,实际问题还是 sort 的问题。