一、什么是归并排序

归并排序又称合并排序,它是成功应用分治技术的一个完美例子。对于一个需要排序的数组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. Android 自定义 view(三)—— onDraw 方法理解

    前言: 上一篇已经介绍了用自己定义的属性怎么简单定义一个view<Android 自定义view(二) -- attr 使用>,那么接下来我们继续深究自定义view,下一步将要去简单理解自 ...

  2. LeetCode: Product of Array Except Self

    Dynamic Programming public class Solution { public int[] productExceptSelf(int[] nums) { int[] ans = ...

  3. Mysql如何向存在外键的数据表中插入数据

    1.创建表 CREATE TABLE `trn_comment_msg` ( `comMsgId` ) NOT NULL AUTO_INCREMENT COMMENT '评论消息主键', `msgId ...

  4. C++笔记(一)

    一.动态数组 一般我们定义数组都是用下面的方式: int str[100]; 但对于一些需要手动输入数组大小的情况,这种定义方式就行不通了.因为上面这种方式要求事先知道数组大小,并且给数组中括号中必须 ...

  5. 关于跨域GET、POST请求的小结//////////////////////zzzzzzz

    JQuery:$.ajax/$.getJSON支持jsonp格式的跨域,但是只支持GET方式,暂不支持POST: so,跨域POST是个值得研究的问题啊!万能的JQuery无法跨域POST:鉴于基本国 ...

  6. C语言回顾-字符串指针

    1.字符串指针 char *变量名="字符串内容"; char ch='b'; char *p1=&ch; char *str="C Language" ...

  7. Jquery EeasyUI等框架中图标的处理方法

    在使用Query EasyUI.Ext等框架开发项目的时候,经常会用到很多小的图标,常见几个图片应用方式总结如下: 一.在jQuery Easyui中添加小图标 1.添加图标的两小步: 先到theme ...

  8. [转]Android SDK更新 Connection to http://dl-ssl.google.com refused 解决方法

    问题描述 使用SDK Manager更新时出现问题Failed to fetch URL https://dl-ssl.google.com/android/repository/repository ...

  9. Node.js入门教程:Node.js如何安装配置并部署第一个网站

    前言:作为一个资深的前端开发人员,不懂的Node.js 那你绝对是不能跟别人说你是资深的前端程序猿滴! 今天洋哥就来和大家一起学习被大牛称之为前端必学的技能之一Node! 那么Node到底是什么呢? ...

  10. 同一界面放两个TTIWDBAdvWebGrid的问题(delphi IW TMS)

    同一个界面放了两个 TTIWDBAdvWebGrid 时,操作一个表,另一个的也跟着一起更改了,后查看网页生成的DIV,发现ID相同. 后查,有一个ID属性,更改后解决问题.