本文我准备用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]