Java线程安全排序同步列表:是否需额外synchronized块?
线程安全列表排序的正确实现分析
首先明确核心结论:你必须把Collections.sort(myListToOrder)放在synchronized块中,原因如下:
Collections.synchronizedList仅保证列表的单个方法调用是线程安全的,比如add、get这类原子操作。但Collections.sort是复合操作:它会遍历列表、比较元素、调整元素位置,整个过程需要多次调用列表方法,并非原子操作。如果不手动加锁,排序过程中其他线程对列表的修改(如增删元素)会引发并发修改异常,或导致排序结果混乱。
但你的代码存在几个关键问题:
- 每次创建新的
synchronizedList实例毫无意义
你每次调用sortSynchronized都用Collections.synchronizedList(this.myList)创建新包装列表,这个新实例的锁对象和其他可能存在的synchronizedList包装实例(若有的话)不一致,无法保证对原myList的并发访问安全。而且原myList是ArrayList(非线程安全),若其他线程直接操作myList,仍会和排序操作产生冲突。 - 锁对象选择错误
你锁的是临时创建的synchronizedList,但如果其他线程也通过synchronizedList包装myList并操作,它们的锁对象不同,无法实现互斥。
正确的实现方案
方案一:将原列表初始化为线程安全列表
直接把myList初始化为synchronizedList,所有操作基于这个全局实例:
protected final List<String> myList = Collections.synchronizedList(Lists.newArrayList()); private List<String> sortSynchronized() { // 锁全局的线程安全列表实例 synchronized (myList) { Collections.sort(myList); } return myList; }
这样所有对myList的操作(包括排序)都通过同一个锁对象互斥,保证线程安全。
方案二:保留原ArrayList,但统一锁原列表
如果必须保留myList为ArrayList,所有涉及myList的操作(排序、增删改查)都必须锁myList本身:
protected final List<String> myList = Lists.newArrayList(); private List<String> sortSynchronized() { synchronized (myList) { // 先复制原列表到副本,避免锁持有时间过长(可选优化) List<String> sortedList = new ArrayList<>(myList); Collections.sort(sortedList); // 若需要替换原列表,需在锁内完成 myList.clear(); myList.addAll(sortedList); return new ArrayList<>(myList); // 返回副本避免外部直接修改原列表 } }
注意:所有访问myList的地方都必须加synchronized (myList),否则仍会有并发问题。返回副本可避免外部线程直接修改原列表,增强安全性。
额外优化建议
如果排序不需要修改原列表,仅需返回排序后的副本,可在锁内复制原列表,再对副本排序——这样锁的持有时间更短,减少并发等待:
private List<String> sortSynchronized() { List<String> sortedList; synchronized (myList) { sortedList = new ArrayList<>(myList); } Collections.sort(sortedList); return sortedList; }
这种方式既保证了复制原列表时的线程安全,又将排序操作放到锁外,提升并发性能。
内容的提问来源于stack exchange,提问作者Catarina Nogueira
相关产品推荐
相关产品推荐

