多線程處理歸並排序的方法一般為:
假設有n個線程同步處理,就將數組等分成n份,每個線程處理一份,再對最后n個有序數組進行歸並。
為了使對整個算法具有可擴展性,即線程數n可以自定義,筆者將線程類、處理數組類等進行封裝,分為最主要的4個類:Array, Merge, MyThread, MoreThreads
,代碼如下:
/*Array.java*/
import java.util.ArrayList;
/**
* @author duyue
*
* 這個類用來處理數組
*
* 原理:
* 創建待排序數組成功后,需要配合多線程(假設有n個線程)分別排序,
* 需要將數組盡量等分成n個分數組(保存到列表中),由n個線程分別歸
* 並排序,並將各個有序數組(再次保存到列表中),最后整合(不歸並
* 整合)並覆蓋原數組,等待最后歸並。
*/
class Array {
/**
* 構造一個保存數組的列表,用於保存分割后的分數組
*/
static ArrayList<int[]> arrayList = new ArrayList<int[]>();
/**
* @param length 數組長度
* @return 待排序的數組
*/
static int[] createArray(int length) {
int[] array = new int[length];
for (int i = 0; i < length; i++) {
array[i] = (int) (Math.random() * 10000);
}
return array;
}
/**
* @param array 待分割(多線程排序需要)的數組
* @param num 線程數,即要分割的份數
*/
static void divideArray(int[] array, int num) {
int k = 0; //記錄原數組的復制進度,k代表當前數組的復制初始點
for (int i = 0; i < num; i++) {
int point = array.length / num; //分數組的長度
int[] a = new int[0]; //保存分數組
/*考慮到不夠整除的情況,將剩余的項全部放在最后一個分數組中*/
if (i != num - 1) a = new int[point];
if (i == num - 1) a = new int[array.length - k];
/*將array[k, k + a.length -1]復制到a[0, a.length]中*/
System.arraycopy(array, k, a, 0, a.length);
arrayList.add(a); //把得到的分數組保存在列表中
k += point; //移動復制初始點
}
}
/**
* @param newArray 由有序分數組整合(不歸並)的新數組
* @param num 有序分數組的個數,即由num個線程分別排序后得到的數組數,也就是線程數
*/
static void newArray(int[] newArray, int num) {
/*原理與divideArray方法相似*/
int k = 0; //記錄新數組的待整合初始點
/*把列表元素(即數組)逐個復制到新的數組中*/
for (int i = 0; i < num; i++) {
System.arraycopy(arrayList.get(i), 0, newArray, k, arrayList.get(i).length);
k += arrayList.get(i).length; //移動待整合初始點
}
}
}
/*Merge.java*/
/**
* @author duyue
*
* 這是對數組進行歸並排序的類
*/
class Merge {
private int[] temp; //暫時存放待排序數組的temp數組
/**
* @param a 待排序的數組由構造器傳遞到類中
*/
Merge(int[] a) {
temp = new int[a.length];
}
public void sort(int[] a) {
sort(a, 0, a.length - 1);
}
public void sort(int[] a, int low, int high) {
if (low >= high)
return;
int mid = low + (high - low) / 2;
sort(a, low, mid); //將左半邊排序
sort(a, mid + 1, high); //將左半邊排序
merge(a, low, mid, high); //歸並結果
}
/**
* @param a 待歸並的數組,其中a[low,mid]和a[mid+1,high]都是有序的
*/
public void merge(int[] a, int low, int mid, int high) {
int i = low, j = mid + 1;
/*將a[low,high]復制到temp[low,high]*/
System.arraycopy(a, low, temp, low, high - low + 1);
/*歸並到a[low,high]*/
for (int k = low; k <= high; k++) {
if (i > mid)
a[k] = temp[j++];
else if (j > high)
a[k] = temp[i++];
else if (temp[j] < temp[i])
a[k] = temp[j++];
else
a[k] = temp[i++];
}
}
}
/*MyThread.java*/
import java.util.concurrent.CountDownLatch;
/**
* @author duyue
*
* 這個類用來定義線程,使其能夠對數組進行歸並排序處理
*/
class MyThread extends Thread {
public int[] aux; //定義一個數組,用來保存待處理的數組
private CountDownLatch latch; //定義這個類用來等待各個線程都完成工作,再進行下一步操作
/*通過構造器將待處理的數組傳遞到線程的類中*/
public MyThread(int[] aux, CountDownLatch latch) {
this.aux = aux;
this.latch = latch;
}
public void run() {
Merge mergeThread = new Merge(aux);
mergeThread.sort(aux);
latch.countDown();
}
}
/*MoreThreads.java*/
import java.util.ArrayList;
import java.util.concurrent.CountDownLatch;
/**
* @author duyue
*
* 本類是多線程處理歸並排序的核心部分。
*
* 原理:
* 由用戶指定線程數,例如n個線程,將數組分為n份,分別用n個線程對這n個數組進行歸並排序,
* 得到n個有序分數組,再對這n個有序數組歸並就得出最后的結果。
* 線程數越多,相應的速度就會越快。
* 要處理的數組長度越長,多線程與單線程的對比就越大。
*/
class MoreThreads {
/**
* @param num 線程數,由用戶定義
*/
MoreThreads(int num) {
System.out.println("現在是" + num + "個線程處理歸並排序:");
int length = 100; //數組總長度
for (int j = 0; j < 6; j++) {
/*記錄起始時間*/
long beginTime = System.currentTimeMillis();
/*創建待排序的數組*/
int[] myArray = Array.createArray(length);
/*將數組近乎等分成num份,以便利用多線程對各個數組排序*/
Array.divideArray(myArray, num);
/*
* 對各個數組利用num個線程同步排序。
* 將num個線程保存在列表threads中,方便將各個線程處理后的數組調出。
* CountDownLatch類用於等待所有的線程都工作完成后,進行最終的歸並。
*/
ArrayList<MyThread> threads = new ArrayList<MyThread>();
CountDownLatch latch = new CountDownLatch(num);
for (int i = 0; i < num; i++) {
MyThread thread = new MyThread(Array.arrayList.get(i), latch);
thread.start();
threads.add(thread);
}
try {
latch.await();
} catch (InterruptedException e) {
e.printStackTrace();
}
/*
* 清空原裝有數組列表中的元素,
* 將排序后的各個數組從threads列表中調出,添加到數組列表Array中
*/
Array.arrayList.clear();
for (int i = 0; i < num; i++) {
Array.arrayList.add(threads.get(i).aux);
}
/*把各個排序后數組規整到長數組中,並對三個有序數組歸並到一個數組中*/
Array.newArray(myArray, num);
/*
* 對有序數組進行歸並
* 歸並原理:
* 將第一個有序分數組(即第一個線程排序后的數組)與其下一個有序分數組(即第二個線程
* 排序后的數組)歸並成一個數組,再把歸並的數組與其下一個有序分數組(即第三個線程排
* 序后的數組)歸並,依此類推.
*/
int low = 0; //整合后的長數組myArray的首位
int mid = -1; //待歸並的第一個有序分數組的末位
int high; //待歸並的第二個有序分數組的末位
for (int i = 0; i < num - 1; i++) {
Merge merge = new Merge(myArray);
mid = Array.arrayList.get(i).length + mid;
high = mid + Array.arrayList.get(i + 1).length;
merge.merge(myArray, low, mid, high);
}
/*記錄結束時間*/
long endTime = System.currentTimeMillis();
System.out.println(length + "項數組歸並排序的時間:" + (endTime - beginTime) + "ms");
length = length * 10;
Array.arrayList.clear(); //清空列表內容,對下一次循環不造成影響
}
}
}
運行以下代碼即可測試:
/*TestThread*/
import java.util.Scanner;
/**
* @author duyue
*
* 這是一個測試類,用於展示結果。
*/
public class TestThread {
public static void main(String[] args) {
new MoreThreads(1);
System.out.println("--------------------------------");
new MoreThreads(2);
System.out.println("--------------------------------");
new MoreThreads(3);
System.out.println("--------------------------------");
System.out.println("你還想嘗試更多線程處理歸並排序嗎?(y:yes, n:no)");
while (true) {
Scanner in = new Scanner(System.in);
String s = in.nextLine();
if (s.equals("n")) {
System.out.println("byebye!");
in.close();
break;
} else if (s.equals("y")) {
System.out.println("請輸入要嘗試的線程數:");
new MoreThreads(in.nextInt());
System.out.println("--------------------------------");
System.out.println("你還想嘗試更多線程處理歸並排序嗎?(y:yes, n:no)");
} else
System.out.println("輸入錯誤!請重新輸入");
}
}
}