本文我準備用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]