并发场景下Collections.shuffle(List<?> list)使元素重复的问题

并发场景下Collections.shuffle(List<?> list)需要注意的问题

场景:

有一个需求需要每个人每次看到的列表是乱序的,使用了Collections.shuffle(List<?> list)来完成这个乱序的功能,但是发现过一段时间就会出现原来没有重复元素的list变重复了。

复现:

public class CollectionShuffle extends Thread{
    private static List<Integer> STATIC_LIST = new ArrayList<>(Arrays.asList(1, 2, 3, 4));

    @Override
    public void run(){
        Collections.shuffle(STATIC_LIST);
    }

    public static void main(String[] args) {
        long startTime = System.currentTimeMillis();
        for (int i = 0; i < 100; i ++) {
            CollectionShuffle thread = new CollectionShuffle();
            thread.start();
        }
        System.out.println(System.currentTimeMillis() - startTime + "ms");
        System.out.println(Arrays.toString(STATIC_LIST.toArray()));
    }
}
10ms
[2, 4, 4, 2]

原因解读:

查看shuffle方法的底层发现,这个方法底层的实现是一个void类型的,是将list转成一个新的Object arr[]对象,然后通过Random随机数来在原集合上修改,这就导致了其他线程可以读到正在进行排序还未完成的集合,此时集合中是有重复对象的。

public static void shuffle(List<?> list, Random rnd) {
        int size = list.size();
        if (size < SHUFFLE_THRESHOLD || list instanceof RandomAccess) {
            for (int i=size; i>1; i--)
                swap(list, i-1, rnd.nextInt(i));
        } else {
            Object arr[] = list.toArray();
            // Shuffle array
            for (int i=size; i>1; i--)
                swap(arr, i-1, rnd.nextInt(i));
            ListIterator it = list.listIterator();
            for (int i=0; i<arr.length; i++) {
                it.next();
                it.set(arr[i]);
            }
        }
    }

解决方法1:

每个线程执行shuffle方法前,都从全局变量中拷贝一份即可解决该问题

	@Override
    public void run(){
        STATIC_LIST = new ArrayList<>(STATIC_LIST);
        Collections.shuffle(STATIC_LIST);
    }
11ms
[2, 1, 3, 4]

版权声明:本文为u012198023原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接和本声明。