一、什么是归并排序

归并排序又称合并排序,它是成功应用分治技术的一个完美例子。对于一个需要排序的数组A[0..n-1],归并排序把它一分为二:A[0..n/2-1]和A[n/2..n-1],并对每个子数组递归排序,然后把这两个排好序的子数组合并为一个有序数组。下面是归并排序的例子图解:

二、单线程实现归并排序

package com.bob.algorithms.sort;

import java.util.Arrays;

import com.bob.algorithms.SortStrategy;

/**
* 归并排序
*
* @author bob
*
*/
public class SingleThreadMergeSort implements SortStrategy { public int[] sort(int[] rawArray) {
mergeSort(rawArray);
return rawArray;
} /**
* 分解并合并排序,升序
*
* @param intArr
*/
private void mergeSort(int[] intArr) {
if (intArr.length > 1) {
// 如果数组长度大于1就分解称两份
int[] leftArray = Arrays.copyOfRange(intArr, 0, intArr.length / 2);
int[] rightArray = Arrays.copyOfRange(intArr, intArr.length / 2, intArr.length);
mergeSort(leftArray);
mergeSort(rightArray); // 合并且排序
merge(leftArray, rightArray, intArr);
}
} /**
* 合并排序
*
* @param leftArray
* @param rightArray
* @param intArr
*/
private void merge(int[] leftArray, int[] rightArray, int[] intArr) { // i:leftArray数组索引,j:rightArray数组索引,k:intArr数组索引
int i = 0, j = 0, k = 0;
while (i < leftArray.length && j < rightArray.length) {
// 当两个数组中都有值的时候,比较当前元素进行选择
if (leftArray[i] < rightArray[j]) {
intArr[k] = leftArray[i];
i++;
} else {
intArr[k] = rightArray[j];
j++;
}
k++;
} // 将还剩余元素没有遍历完的数组直接追加到intArr后面
if (i == leftArray.length) {
for (; j < rightArray.length; j++, k++) {
intArr[k] = rightArray[j];
}
} else {
for (; i < leftArray.length; i++, k++) {
intArr[k] = leftArray[i];
}
}
}
}

三、使用Fork/Join框架实现归并排序

Fork/Join是从JDK 1.7 加入的并发计算框架。

package com.bob.algorithms.sort;

import java.util.Arrays;
import java.util.concurrent.ForkJoinPool;
import java.util.concurrent.RecursiveAction; import com.bob.algorithms.SortStrategy; public class ForkJoinMergeSort implements SortStrategy { public int[] sort(int[] rawArray) {
ForkJoinPool pool = new ForkJoinPool();
pool.invoke(new MergeSort(rawArray));
return rawArray;
} /**
* 使用Fork/join的方式进行归并排序,充分利用cpu
*
* @author zhangwensha
*
*/
private static class MergeSort extends RecursiveAction { private static final long serialVersionUID = 425572392953885545L;
private int[] intArr; public MergeSort(int[] intArr) {
this.intArr = intArr;
} @Override
protected void compute() {
if (intArr.length > 1) {
// 如果数组长度大于1就分解称两份
int[] leftArray = Arrays.copyOfRange(intArr, 0, intArr.length / 2);
int[] rightArray = Arrays.copyOfRange(intArr, intArr.length / 2, intArr.length); // 这里分成两份执行
invokeAll(new MergeSort(leftArray), new MergeSort(rightArray)); // 合并且排序
merge(leftArray, rightArray, intArr);
}
} /**
* 合并排序
*
* @param leftArray
* @param rightArray
* @param intArr
*/
private void merge(int[] leftArray, int[] rightArray, int[] intArr) { // i:leftArray数组索引,j:rightArray数组索引,k:intArr数组索引
int i = 0, j = 0, k = 0;
while (i < leftArray.length && j < rightArray.length) {
// 当两个数组中都有值的时候,比较当前元素进行选择
if (leftArray[i] < rightArray[j]) {
intArr[k] = leftArray[i];
i++;
} else {
intArr[k] = rightArray[j];
j++;
}
k++;
} // 将还剩余元素没有遍历完的数组直接追加到intArr后面
if (i == leftArray.length) {
for (; j < rightArray.length; j++, k++) {
intArr[k] = rightArray[j];
}
} else {
for (; i < leftArray.length; i++, k++) {
intArr[k] = leftArray[i];
}
}
} }
}
 

四、单线程 pk 多线程

编写了舞台类,通过调整generateIntArray(10000000)的输入参数来设置待排序数组长度,试验中没有对堆容量进行设置。

package com.bob.algorithms;

import java.util.Arrays;
import java.util.Date; import com.bob.algorithms.common.CommonUtil;
import com.bob.algorithms.sort.ForkJoinMergeSort;
import com.bob.algorithms.sort.SingleThreadMergeSort; /**
* 舞台类,专门用来测试算法的时间
*
* @author bob
*
*/
public class Stage { public static void main(String[] args) { // 变量定义
long begintime = 0;
long endtime = 0; // 生成排序数据
int[] rawArr = generateIntArray(10000000);
int[] rawArr2 = Arrays.copyOf(rawArr, rawArr.length); begintime = new Date().getTime();
new SingleThreadMergeSort().sort(rawArr);
//System.out.println(Arrays.toString(new SingleThreadMergeSort().sort(rawArr)));
endtime = new Date().getTime();
System.out.println("单线程归并排序花费时间:" + (endtime - begintime));
System.out.println("是否升序:"+CommonUtil.isSorted(rawArr, true)); begintime = new Date().getTime();
new ForkJoinMergeSort().sort(rawArr2);
//System.out.println(Arrays.toString(new ForkJoinMergeSort().sort(rawArr2)));
endtime = new Date().getTime();
System.out.println("Fork/Join归并排序花费时间:" + (endtime - begintime));
System.out.println("是否升序:"+CommonUtil.isSorted(rawArr2, true));
} /**
* 生成int类型的数组
*
* @return
*/
private static int[] generateIntArray(int length) {
int[] intArr = new int[length];
for (int i = 0; i < length; i++) {
intArr[i] = new Double(Math.random() * length).intValue();
}
return intArr;
}
}
 

以下是数组容量在各个量级时,两种方法效率对比:

数组长度 100 1000 10000 100000 1000000 10000000
单线程 (ms) 1 2 7 33 188 2139
Fork/Join (ms) 8 9 17 63 358 1133

通过统计可以发现,当待排序序列长度较小时,使用单线程效率要高于多线程,但是随着数量不断增加,多线程执行时间越来越接近单线程的执行时间,最终在1000万这个量级开始速率远超单线程。工作中不能滥用多线程,在该使用的时候使用可以加快效率,充分利用多核。但是在不该用的时候使用徒增工作量,有可能效率还不如单线程。 感兴趣的朋友可以通过下面代码地址找到运行的全部源码自己跑跑试试看。

五、本文代码地址

包括本篇在内以后所有代码统一存放地址为: 
https://github.com/mingbozhang/algorithm

六、参考

https://docs.oracle.com/javase/tutorial/essential/concurrency/forkjoin.html 
《算法设计与分析基础(第3版)》

java归并排序,单线程vs多线程的更多相关文章

  1. Java IO学习笔记七:多路复用从单线程到多线程

    作者:Grey 原文地址:Java IO学习笔记七:多路复用从单线程到多线程 在前面提到的多路复用的服务端代码中, 我们在处理读数据的同时,也处理了写事件: public void readHandl ...

  2. java 并发性和多线程 -- 读感 (一 线程的基本概念部分)

    1.目录略览      线程的基本概念:介绍线程的优点,代价,并发编程的模型.如何创建运行java 线程.      线程间通讯的机制:竞态条件与临界区,线程安全和共享资源与不可变性.java内存模型 ...

  3. Java 并发性和多线程

    一.介绍 在过去单 CPU 时代,单任务在一个时间点只能执行单一程序.之后发展到多任务阶段,计算机能在同一时间点并行执行多任务或多进程.虽然并不是真正意义上的“同一时间点”,而是多个任务或进程共享一个 ...

  4. Java 并发和多线程(一) Java并发性和多线程介绍[转]

    作者:Jakob Jenkov 译者:Simon-SZ  校对:方腾飞 http://tutorials.jenkov.com/java-concurrency/index.html 在过去单CPU时 ...

  5. zookeeper的c API 单线程与多线程问题 cli_st和cli_mt

    同样的程序,在centos和ubuntu上都没有问题,在solaris上问题却多多,据说是solaris管理更加严格. zookeeper_init方法,在传入一个错误的host也能初始化出一个非空的 ...

  6. Java简明教程 12.多线程(multithreading)

    单线程和多线程 关于它们的区别,zhihu上有一个回答,我认为十分不错,如下: . 单进程单线程:一个人在一个桌子上吃菜. . 单进程多线程:多个人在同一个桌子上一起吃菜. . 多进程单线程:多个人每 ...

  7. 1、Java并发性和多线程-并发性和多线程介绍

    以下内容转自http://ifeve.com/java-concurrency-thread/: 在过去单CPU时代,单任务在一个时间点只能执行单一程序.之后发展到多任务阶段,计算机能在同一时间点并行 ...

  8. Spring Boot 定时任务单线程和多线程

    Spring Boot 的定时任务: 第一种:把参数配置到.properties文件中: 代码: package com.accord.task; import java.text.SimpleDat ...

  9. Java基础】并发 - 多线程

    Java基础]并发 - 多线程 分类: Java2014-05-03 23:56 275人阅读 评论(0) 收藏 举报 Java   目录(?)[+]   介绍 Java多线程 多线程任务执行 大多数 ...

随机推荐

  1. java实现甘特图的2种方法:SwiftGantt和Jfree (转)

    http://blog.sina.com.cn/s/blog_50a7c4a601009817.html 第一种方法使用SwiftGantt实现甘特图(进度图推荐这个)   import java.a ...

  2. ios webView 放大网页解决/input 获得焦点focus 网页放大 解决

    新手遇到的问题: 终于找到原因,各种HTML viewport 都试过 setScalePageToFit 也试过,webViewDidFinishLoad加JS代码,动态算webView.scrol ...

  3. sharepont 2013 隐藏Ribbon 菜单

    引用:C:\Program Files\Common Files\Microsoft Shared\Web Server Extensions\15\ISAPI\Microsoft.Web.Comma ...

  4. Redmine开发帮助

    这里先零星记录二次开发用得上的知识点: 1.windows下开发环境,参考此文.最好使用rubyinstaller安装,注意选择版本.或者直接安装railsinstaller. 2.获取自定义内容,参 ...

  5. ODBC简介

    加载驱动 1 oracle Class.forName("oracle.JDBC.driver.OracleDriver") 2 DB2 Class.forName("c ...

  6. 使用JDOM操作XML

    JDOM介绍 JDOM是使用Java语言编写的,用于读写及操作XML的一套组件,Jdom同时具有DOM修改文件的优点和SAX读取快速的优点. JDOM的使用 首先下载JDOM的JAR包,本文使用的是j ...

  7. [解决方案]vs2015无法解析外部符号__imp__fprintf和__imp____iob_func

    转自:http://www.cnblogs.com/ubosm/p/5444919.html 使用vs2015编译ffmpeg的一个小项时,出现了__imp__fprintf和__imp____iob ...

  8. android xml中的xliff属性

    <resources xmlns:android="http://schemas.android.com/apk/res/android" xmlns:xliff=" ...

  9. 对C++虚函数的理解

    关于类不断被继承的过程,从整体上看,是一个从抽象到逐渐具体化的过程,基类可以是非常非常抽象的东西,而最终实例化的派生类就非常具体了. 虚函数的意义,就在于定义了一个从最早的基类到最终的派生类都可能会用 ...

  10. USB 设备的PID-Product ID,VID-Vendor ID

    根据USB规范的规定,所有的USB设备都有供应商ID(VID)和产品识别码(PID),主机通过不同的VID和PID来区别不同的设备,VID 和PID都是两个字节长,其中,供应商ID(VID)由供应商向 ...