算法:用Java實現睡眠排序(SleepSort)

本文我準備用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]
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章