本文我准备用Java实现睡眠排序。睡眠排序由于其独有的排序方式,排序数字最好是非负整数,且最大值不要太大,否则算法会运行很久……非负小数其实也可以,但是排序后的相邻小数的差值不要太小,否则可能会出错,因为多线程的运行有其不确定性和延迟的可能……

虽然睡眠排序挺欢乐的,但是想写好一个睡眠排序也挺不容易的,涉及到多线程的设计、启动、运行,以及控制的方法,可以算是多线程编程的一次小小实战!本次睡眠排序,我用CountDownLatch类来控制多线程,Executors.newCachedThreadPool() 线程池运行多线程

具体的排序算法过程已经在注释里面了,大家可以复制代码到IDE里面,用DEBUG模式研究算法的过程:

import java.util.*;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

/**
 * @author LiYang
 * @ClassName SleepSort
 * @Description 睡眠排序算法
 * @date 2019/11/11 14:31
 */
public class SleepSort {

    /**
     * 睡眠者类,也就是睡眠排序操作多线程类,让当前线程睡眠排序数字
     * 那么多秒……睡醒后,就将排序数放入List,完成当前数字的睡眠排序
     */
    static class Sleeper implements Runnable {

        //需要排序的数字之一,也就是睡多少秒
        private int sleepSeconds;
        
        //开始的倒数栅栏,用于控制大家一起开始睡
        private CountDownLatch startLatch;
        
        //结束的倒数栅栏,用于等大家都睡醒了,整理并返回排序结果
        private CountDownLatch finishLatch;
        
        //记录排序结果的List,需要是线程安全的
        private List result;

        /**
         * 睡眠者类的构造方法,用构造传入参数
         * @param sleepSeconds 排序数组中的数字之一,也就是睡多少秒
         * @param startLatch 开始的倒数栅栏,用于控制大家一起开始睡
         * @param finishLatch 结束的倒数栅栏,用于等大家都睡醒了,整理并返回排序结果
         * @param result 记录排序结果的List,需要是线程安全的
         */
        public Sleeper(int sleepSeconds, CountDownLatch startLatch, CountDownLatch finishLatch, List result){
            this.sleepSeconds = sleepSeconds;
            this.startLatch = startLatch;
            this.finishLatch = finishLatch;
            this.result = result;
        }

        /**
         * 睡眠排序的实现方法
         */
        @Override
        public void run() {
            try {
                //start倒数栅栏阻塞,等待所有排序线程就绪
                startLatch.await();
                
                //所有排序线程都启动后,大家同一时间一起睡
                //睡的秒数,就是排序的数字
                Thread.sleep(sleepSeconds * 1000);
                
                //睡醒后,将该排序数放入结果list
                result.add(sleepSeconds);
                
                //结束的倒数栅栏计数减一,以便于大家都睡醒并将结果放入List了
                //主线程统一处理结果List,返回睡眠排序的有序结果
                finishLatch.countDown();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }

    /**
     * 睡眠排序算法(SleepSort)
     * 
     * 这个睡眠排序算法虽然挺欢乐的,但是实现该排序算法
     * 也不简单,涉及到多线程的设计、启动、运行,以及控制
     * 的方法,这里我用CountDownLatch类来控制多线程,
     * Executors.newCachedThreadPool() 线程池运行多线程
     * 
     * 注意:睡眠排序最好是非负整数,且最大值不要太大,
     * 否则算法会运行很久……非负小数其实也可以,但是
     * 排序后的相邻小数的差值不要太小,否则可能会出错,
     * 因为多线程的运行有其不确定性和延迟的可能
     * @param arr 待排序数组
     * @return 运行睡眠排序后,排好序的结果
     */
    public static int[] sleepSort(int[] arr){
        //待返回的排好序的数组
        int[] sorted = new int[arr.length];
        
        try {
            /**
             * 用于统计排序结果的list,需要线程安全,可以选的线程安全List如下:
             * 1、Vector:老牌线程安全list,内部操作方法全用synchronized修饰
             * 2、CopyOnWriteArrayList:写时复制List,用原子操作更新内部数组,
             *      写操作(如add()操作)的时候需要数组复制,适用于读多写少的场景
             * 3、Collections.synchronizedList():入参一个List的子类实例,返回
             *      一个线程安全的List
             */
            List result = Collections.synchronizedList(new ArrayList<>());

            //开始的倒数栅栏,用以控制所有睡眠排序线程同时同步开始运行
            CountDownLatch startLatch = new CountDownLatch(1);
            
            //结束的倒数栅栏,用以阻塞睡眠排序结束的线程,等所有睡眠
            //线程醒来,并加入自己的睡眠秒数后,取消阻塞所有的睡眠排序
            //线程,以便于保证所有数字都加入了结果List,然后再统一处理
            CountDownLatch finishLatch = new CountDownLatch(arr.length);

            //线程池框架实例,用于执行睡眠排序的多个排序线程
            ExecutorService executor = Executors.newCachedThreadPool();

            //将待排序数组的所有元素,每一个都开启一个睡眠排序线程
            for (int i = 0; i < arr.length; i++) {
                
                //创建当前排序数字的睡眠者类实例,线程睡眠排序数字那么多秒之后,
                //将结果按顺序加入结果List的最末端,实现睡眠排序
                Sleeper sleeper = new Sleeper(arr[i], startLatch, finishLatch, result);
                
                //执行当前排序数字的睡眠者类实例
                executor.execute(sleeper);
            }

            //所有待排序数字的睡眠者类线程都创建完成并启动后,
            //放开start倒数栅栏,大家同时一起睡
            //因为start倒数栅栏构造入参1,所以countDown()只需要调用1次
            startLatch.countDown();

            //finish倒数栅栏阻塞,直到所有睡眠者类线程都完成
            //睡眠排序,并提交结果后,才放开finish倒数栅栏
            //然后处理睡眠排序后的结果
            finishLatch.await();

            //现在所有排序线程均已完成排序,并提交了结果
            //接下来将睡眠排序后的List,整理为数组
            for (int i = 0; i < result.size(); i++) {
                sorted[i] = (int) result.get(i);
            }
            
            //调用线程池框架的shutdown()方法
            //优雅地关闭线程池,结束它的使命
            executor.shutdown();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        
        //返回睡眠排序后的有序数组
        return sorted;
    }

    /**
     * 验证睡眠排序算法
     * @param args
     */
    public static void main(String[] args) {
        //待排序数组
        int[] arr = new int[16];

        //随机数类
        Random random = new Random();

        //随机生成排序数组(10以内的整数)
        //当然,本例的排序时间会在至多10秒之内完成
        //该算法运行时间,取决于排序数组中最大的数字
        for (int i = 0; i < arr.length; i++) {
            arr[i] = random.nextInt(10);
        }

        //打印待排序数组
        System.out.println("睡眠排序前:" + Arrays.toString(arr));

        //进行睡眠排序,返回排好序的新数组
        int[] sleepSortedArr = sleepSort(arr);

        //这里可以将排好序的数组,重新赋给原来的数组,保持之前的操作
        arr = sleepSortedArr;

        //打印睡眠排序后的数组
        System.out.println("睡眠排序后:" + Arrays.toString(arr));
    }
    
}

运行 SleepSort 类的main方法,等上一段时间,睡眠排序算法测试通过:

睡眠排序前:[3, 3, 8, 7, 8, 7, 6, 3, 9, 3, 2, 2, 5, 8, 0, 1]
睡眠排序后:[0, 1, 2, 2, 3, 3, 3, 3, 5, 6, 7, 7, 8, 8, 8, 9]