【Java并发编程】Fork/join 并发编程模型,让多核cpu发挥最大优势。

并发编程,是老生常谈的问题了,并发编程能够真正的让多核cpu发挥最大的优势。

现在我们来玩一下Java Fork/join 并发编程模型^_^

 

Fork/Join框架是Java7提供的一个用于并行计算的框架,它主要是用于把一个大任务拆分为若干个小任务,然后把若干个小任务的结果再汇总为大任务的结果。

这种并行模式特别适合于基于分治法(Divide and conquer)的计算。

任务拆分如图:

这样能够把一个任务拆分成多个互相独立不影响的子任务,能够进行并行计算。

 

1.如何利用Fork/Join来进行编程?

先看两个类 RecursiveTask和 RecursiveAction,继承者两个类(两者区别在于前者重写方法有返回值,后者无),然后重写compute方法就可以进行并行编程,非常方便。

代码如图:

import java.util.concurrent.RecursiveAction;
import java.util.concurrent.RecursiveTask;

public class RecursiveTaskTest extends RecursiveTask<Object> {

    @Override
    protected Object compute() {
        
        return null;
    }

}

class RecuriveActionTest extends RecursiveAction {

    @Override
    protected void compute() {
 
    }

}

我们现在使用上述模型来对我们平时常用的快速排序(Quick Sort)算法进行改进。

(不了解快排怎么实现的自行补习,传送门http://blog.csdn.net/morewindows/article/details/6684558)

一般地,快速排序中又如下代码:

    private void originalQuickSort( int[] array, int start, int end ) {
        if( start > end ) {
            return;
        }
        int p = partition( array, start, end );
        originalQuickSort( array, start, p - 1 );
        originalQuickSort( array, p + 1, end );
    }

 

存在这拆分子任务的情况,所以我们可以对传统快排进行改进,把此代码修改成如下形式(基于RecusiveAction)

 

    protected void compute() {
        if( start > end ) {
            return;
        }
        int p = partition( array, start, end );
        new QuickSortTest( array, start, p - 1 ).fork();
        new QuickSortTest( array, p + 1, end ).fork();
    }

 

 

然后简单快排、JDK内置排序、并行编程快排三个排序进行效率比较(n=10^8):

(由于是在windows下编写的程序,可以利用window的任务管理器观看cpu负载情况,并且能够反映出Fork/Join框架是否真正的利用了多核心cpu的优势。)

 

机器配置:

内存:16G

Cpu:i7-4790 3.6Ghz

 

完整测试代码:

(为了保证变量一致,设置三个相同的数组进行排序)

import java.util.Arrays;
import java.util.concurrent.ForkJoinPool;
import java.util.concurrent.RecursiveAction;
import java.util.concurrent.TimeUnit;

public class QuickSortTest extends RecursiveAction {

    public int array[] = null;

    private int start;

    private int end;

    public QuickSortTest( int[] array, int start, int end ) {
        this.array = array;
        this.start = start;
        this.end = end;
    }

    public QuickSortTest( int[] array ) {
        this.array = array;
    }

    private int partition( int[] array, int beg, int end ) {
        int first = array[ beg ];
        int i = beg, j = end;
        while( i < j ) {
            while( array[ i ] <= first && i < end ) {
                i++;
            }
            while( array[ j ] > first && j >= beg ) {
                j--;
            }
            if( i < j ) {
                array[ i ] = array[ i ] ^ array[ j ];
                array[ j ] = array[ i ] ^ array[ j ];
                array[ i ] = array[ i ] ^ array[ j ];
            }
        }
        if( j != beg ) {
            array[ j ] = array[ beg ] ^ array[ j ];
            array[ beg ] = array[ beg ] ^ array[ j ];
            array[ j ] = array[ beg ] ^ array[ j ];
        }
        return j;
    }

    private void originalQuickSort( int[] array, int start, int end ) {
        if( start > end ) {
            return;
        }
        int p = partition( array, start, end );
        originalQuickSort( array, start, p - 1 );
        originalQuickSort( array, p + 1, end );
    }

    @Override
    protected void compute() {
        if( start > end ) {
            return;
        }
        int p = partition( array, start, end );
        new QuickSortTest( array, start, p - 1 ).fork();
        new QuickSortTest( array, p + 1, end ).fork();
    }

    public static void main( String[] args ) {
        int LENGTH = 50000000 * 2;
        int[] array1 = new int[ LENGTH ];
        int[] array2 = new int[ LENGTH ];
        int[] array3 = new int[ LENGTH ];
        for( int i = 0; i < LENGTH; i++ ) {
            int n = ( int )( Math.random() * Integer.MAX_VALUE ) % Integer.MAX_VALUE;
            array1[ i ] = n;
            array2[ i ] = n;
            array3[ i ] = n;
        }
        
        System.out.println( "run" );
        
        //简单快排
        QuickSortTest qst = new QuickSortTest( array2 );
        long startTime = System.currentTimeMillis();
        qst.originalQuickSort( array2, 0, array2.length - 1 );
        System.out.println( "original quick sort : " + ( float )( System.currentTimeMillis() - startTime ) / 1000f + "s" );
        
        
        //Fork/Join并行计算版本
        startTime = System.currentTimeMillis();
        ForkJoinPool fjp = new ForkJoinPool();
        fjp.submit( new QuickSortTest( array1, 0, array1.length - 1 ) );
        fjp.shutdown();
        try {
            fjp.awaitTermination( 10, TimeUnit.SECONDS );
        } catch( Exception e ) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }
        System.out.println( "multiThread quick sort : " + ( float )( System.currentTimeMillis() - startTime ) / 1000f + "s" );
         
        //jdk排序
        startTime = System.currentTimeMillis();
        Arrays.sort( array3 );
        System.out.println( "jdk sort : " + ( float )( System.currentTimeMillis() - startTime ) / 1000f + "s" );
    }

}
//original quick sort : 13.032s
//multiThread quick sort : 3.502s
//jdk quick sort : 9.033s

 

我们观察cpu的负载情况:

1.简单快排

 

可以看出,cpu整体使用率是有16%左右,可以肯定cpu还是保留了实力的,结果是13s左右完成排序。

 

2.并行计算版本

 

毫不夸张的说,直接使用率直线上升,最高时可以达到100%的使用率,cpu是满载工作的,并且可以看出八个核心的使用率都上来了,充分的证明Fork/Join框架是真正的利用了多个核心的计算能力。结果3.5s左右排序。

(*****Ps.理论上应该是快了8倍左右,实验结果只有4倍左右,其余的消耗应该是在轮转调度上面。******)

 

3.JDK内置排序

JDK内置排序非常复杂,进行了各种优化(插排,TimSort,双轴,三轴快排等),具体我就不多累赘,有兴趣的可以自行研究,结果9s左右完成排序。

 

总结:

      利用多核的技能能力进行并行计算在速度上还是很有优势的。所以,在拥有一台多核计算机进行编程的时候应该多想一想是否充已经分利用多核计算机的计算能力来进行编程了吗?

 

posted @ 2017-03-13 18:45  hudiwei-hdw  阅读(2196)  评论(0编辑  收藏  举报