[Java multiproceso] Sobre el desarrollo de tareas de procesamiento paralelo en Java

I. Introducción

Paralelo , a 多个线程一起运行,来提高系统的整体处理速度saber:.

  • ¿Por qué utilizar múltiples hilos pueden mejorar la velocidad de procesamiento, porque ahora el equipo en general, son 多核处理器, necesitamos cargo 分利用cpu资源; si la estación es un poco más alto, que 每台机器都可以是一个处理节点, 多台机器并行处理. Se puede decir que el procesamiento en paralelo está en todas partes.

Este artículo habla principalmente sobre la forma en que Java implementa el procesamiento paralelo

2. Paralelo en todas partes

Recolector de basura de Java, podemos ver que todas las versiones de generación de la actualización, junto con los GC retrasos más cortos, desde serialla cmsvez en cuando G1, Java ha sido lento para despegar el sombrero. Desde la primera cola de mensajes ActiveMQhasta el presente kafkay RocketMQla introducción del 分区concepto, para mejorar el paralelismo del mensaje. Después de que los datos de una sola tabla en la base de datos alcancen un cierto nivel, la velocidad de acceso será muy lenta. 分表Procesaremos e importaremos la tabla 数据库中间件; puede pensar que el procesamiento de Redis en sí es de un solo subproceso, pero slot(槽)el concepto introducido en la solución de clúster de Redis ; más general Son muchos de nuestros sistemas comerciales, que generalmente implementan múltiples unidades y las 负载均衡中间件distribuyen a través de ellas; bueno, hay algunos otros ejemplos, pero no los enumeraré todos aquí.

Recolector de basura Java: serie, paralelo, CMS, G1 Descripción general del recopilador
JVM Recolector de basura: comparación de serie, paralelo, CMS y G1

3. Cómo conectar en paralelo

Creo que el paralelo es el núcleo de eso "拆分", 把大任务变成小任务,然后利用多核CPU也好,还是多节点也好,同时并行的处理la versión antigua de la actualización de Java, se procesa en paralelo para proporcionar más conveniencia a nuestros desarrolladores, desde el principio Thread, hasta 线程池, hasta fork/join框架, para finalmente tratar

Usemos un ejemplo de suma simple para ver cómo se procesan varios métodos en paralelo;

3.1. Procesamiento de un solo subproceso

Primero observe el método de procesamiento de un solo subproceso más simple, use directamente el subproceso principal para operaciones de suma;

public class SingleThread {
    
    
    public static void main(String[] args) {
    
    
    	//生成指定范围大小的的数组
        long[] numbers = LongStream.rangeClosed(1, 10_000_000).toArray();
        long sum = 0;
        for (int i = 0; i < numbers.length; i++) {
    
    
            sum += numbers[i];
        }
        System.out.println("sum  = " + sum);
    }
}

Suma sí es una tarea computacionalmente intensivas , pero ahora 多核时代, solamente 单线程, lo que equivale a un solo uso 一个cpu, 其他cpu被闲置,导致资源的浪费.

3.2.Método de hilo

Ponemos 任务拆分成多个小任务,然后每个小任务分别启动一个线程,分段处理任务. Como sigue:

public class ThreadTest {
    
    
    //分段阈值,即每个线程处理次数
    public static final int threshold = 10_000;
    //要累加的数字集合
    public static long[] numbers;
    //累加结果
    private static long allSum;

    public static void main(String[] args) throws Exception {
    
    
        //生成要累加的数字集合
        numbers = LongStream.rangeClosed(1, 10_000_000).toArray();

        //线程数 =计算总次数 / 每个线程处理次数
        int taskSize = (int) (numbers.length / threshold);

        //循环生成线程
        for (int i = 1; i <= taskSize; i++) {
    
    
            final int key = i;
            new Thread(new Runnable() {
    
    
                public void run() {
    
    
                    //一个线程处理数组的一段数据  start= (i - * threshold) ,end = key * threshold,类似于分页计算公式
                    sumAll(segmentSum((key - 1) * threshold, key * threshold));
                }
            }).start();
        }

        Thread.sleep(100);
        System.out.println("allSum = " + getAllSum());
    }

    //累加每个线程计算的总和
    private static synchronized long sumAll(long threadSum) {
    
    
        return allSum += threadSum;
    }

    //获取总和
    public static synchronized long getAllSum() {
    
    
        return allSum;
    }

    /**
     * 分段累加
     * @param start 开始下标
     * @param end   结束下标
     * @return
     */
    private static long segmentSum(int start, int end) {
    
    
        long sum = 0;
        for (int i = start; i < end; i++) {
    
    
            sum += numbers[i];
        }
        return sum;
    }
}

En la sección anterior , una gran tarea se divide en pequeñas tareas. Luego, a través del umbral de segmentación, se calcula el número de subprocesos a generar y el número de tareas a procesar en cada segmento . Este tratamiento es 创建的线程数过多,而CPU数有限, más importante aún 求和是一个计算密集型任务, 启动过多的线程只会带来更多的线程上下文切换. Al mismo tiempo 线程处理完一个任务就终止了,也是对资源的浪费. Además, se puede ver que el subproceso principal no sabe cuándo se ha procesado la subtarea y se requiere un procesamiento adicional. Así que Java introdujo posteriormente grupos de subprocesos .

3.3. Modo de grupo de subprocesos

Introducido en Java 1.5并发包java.concurrent , incluido el grupo de subprocesos ThreadPoolExecutor, el código relevante es el siguiente:

public class ExecutorServiceTest {
    
    
    //分段阈值,即每个线程处理次数
    public static final int threshold = 10_000;
    //要累加的数字集合(即)
    public static long[] numbers;

    public static void main(String[] args) throws Exception {
    
    
        //生成要累加的数字集合
        numbers = LongStream.rangeClosed(1, 10_000_000).toArray();

        //创建固定长度的线程池,核心线程数大于与非核心线程大小相等=cpu核心数+1
        ExecutorService executor = Executors.newFixedThreadPool(Runtime.getRuntime().availableProcessors() + 1);

        //CompletionService实际上可以看做是Executor和BlockingQueue的结合体。CompletionService在接收到要执行的任务时,通过类似BlockingQueue的put和take获得任务执行的结果。CompletionService的一个实现是ExecutorCompletionService,
        CompletionService<Long> completionService = new ExecutorCompletionService<Long>(executor);

        //线程数 =计算总次数 / 每个线程处理次数
        int taskSize = numbers.length / threshold;

        //循环生成线程
        for (int i = 1; i <= taskSize; i++) {
    
    
            final int key = i;
            completionService.submit(new Callable<Long>() {
    
    
                @Override
                public Long call() throws Exception {
    
    
                    //一个线程处理数组的一段数据  start= (i - * threshold) ,end = key * threshold,类似于分页计算公式
                    return segmentSum((key - 1) * threshold, key * threshold);
                }
            });
        }

        long sumValue = 0;
        for (int i = 0; i < taskSize; i++) {
    
    
            //检索并移除表示下一个已完成任务的 Future,如果目前不存在这样的任务,则等待。
            sumValue += completionService.take().get();
        }

        // 所有任务已经完成,关闭线程池
        System.out.println("sumValue = " + sumValue);
        executor.shutdown();
    }

    /**
     * 分段累加
     * @param start 开始下标
     * @param end   结束下标
     * @return
     */
    private static long segmentSum(int start, int end) {
    
    
        long sum = 0;
        for (int i = start; i < end; i++) {
    
    
            sum += numbers[i];
        }
        return sum;
    }
}

El 计算密集型negocio se analizó anteriormente 并不是线程越多越好y se crea aquí JDK默认的线程数:CPU数+1. Este es un resultado que se obtiene después de muchas pruebas; el grupo de subprocesos, como su nombre lo indica,可以重复利用现有的线程

  • Aprovechando CompletionServicepara对子任务进行汇总
  • El uso razonable del grupo de subprocesos ya puede realizar tareas de procesamiento en paralelo, pero la escritura es un poco engorrosa, en este momento se introduce Java1.7fork/join框架 ;

3.4.fork / marco de unión

El propósito del marco de sucursales / fusiones es :; El 以递归的方式将可以并行的任务拆分成更小的任务,然后将每个子任务的结果合并起来生成整体结果código relevante es el siguiente:

public class ForkJoinTest extends RecursiveTask<Long> {
    
    
    //分段阈值,即每个线程处理次数
    public static final int threshold = 10_000;
    //要累加的数字集合(即)
    private final long[] numbers;
    //当前任务集合开始下标
    private final int start;
    //当前任务集合结束下标
    private final int end;

    //构造方法(初始化要累加的数字集合,开始下标,结束下标)
    private ForkJoinTest(long[] numbers, int start, int end) {
    
    
        this.numbers = numbers;
        this.start = start;
        this.end = end;
    }

    public static void main(String[] args) {
    
    
        //要累加的数字集合(即)
        long[] numbers = LongStream.rangeClosed(1, 10_000_000).toArray();


        // 创建包含Runtime.getRuntime().availableProcessors()返回值作为个数的并行线程的ForkJoinPool
        ForkJoinPool forkJoinPool = new ForkJoinPool();

        // 提交可分解的PrintTask任务
        //Future<Long> future = forkJoinPool.submit(new ForkJoinTest(numbers, 0, numbers.length));
        //System.out.println("计算出来的总和="+future.get());

        //创建ForkJoin 任务
        ForkJoinTask<Long> task = new ForkJoinTest(numbers,0, numbers.length);
        Long sumAll = forkJoinPool.invoke(task);
        System.out.println("计算出来的总和=" + sumAll);

        // 关闭线程池
        forkJoinPool.shutdown();
    }

    @Override
    protected Long compute() {
    
    
        //总处理次数
        int length = end - start;
        // 当end-start的值小于threshold时候,直接累加
        if (length <= threshold) {
    
    
            long sum = 0;
            for (int i = start; i < end; i++) {
    
    
                sum += numbers[i];
            }
            return sum;
        }
        
        System.err.println("=====任务分解======");
        // 将大任务从中间切分,然后分解成两个小任务
        int middle = (start + end) / 2;

        //任务分解: 将大任务分解成两个小任务
        ForkJoinTest leftTask = new ForkJoinTest(numbers, start, middle);
        ForkJoinTest rightTask = new ForkJoinTest(numbers, middle, end);

        // 并行执行两个小任务
        leftTask.fork();
        rightTask.fork();

        // 注:join方法会阻塞,因此有必要在两个子任务的计算都开始之后才执行join方法
        // 把两个小任务累加的结果合并起来
        return leftTask.join() + rightTask.join();
    }
}

Resultados del:
Inserte la descripción de la imagen aquí

ForkJoinPoolEs una implementación de la interfaz ExecutorService . Las subtareas se asignan a los subprocesos de trabajo en el grupo de subprocesos. Al mismo tiempo, las tareas deben enviarse a este grupo de subprocesos. RecursiveTask<R>Se debe crear una subclase.

  • La lógica general es 通过fork(0进行拆分, entonces 通过join()进行结果的合并, que Java nos proporciona un framework, solo necesitamos completarlo, lo cual es más conveniente
  • ¿Existe una forma más sencilla de guardar la división y dividir y fusionar automáticamente el concepto Java1.8introducido en ?

3.5. Modo de transmisión en paralelo

Java 8 introdujo el concepto de flujo, que nos permite hacer un mejor uso del paralelismo. El código para usar el flujo es el siguiente:

public class StreamTest {
    
    
    public static void main(String[] args) {
    
    
        // 并行流:多个线程同时运行
        System.out.println("sum = " + parallelRangedSum(10_000_000));
        // 顺序流:使用主线程,单线程
        System.out.println("sum = " + sequentialRangedSum(10_000_000));
    }
    
    //并行流
    public static long parallelRangedSum(long n) {
    
    
        return LongStream.rangeClosed(1, n).parallel().reduce(0L, Long::sum);
    }
    //顺序流
    public static long sequentialRangedSum(long n) {
    
    
        return LongStream.rangeClosed(1, n).sequential().reduce(0L, Long::sum);
    }
}

¿El código anterior es muy simple? Para los desarrolladores, está completo 不需要手动拆分,使用同步机制等方式y las tareas se pueden procesar en paralelo. Solo necesita usar parallel()métodos para las transmisiones 系统自动会对任务进行拆分. Por supuesto, la premisa es que no hay un estado mutable compartido.

  • El uso interno de la secuencia paralela también es el marco de bifurcación / unión

Conclusión
Este artículo utiliza un ejemplo de suma para introducir el procesamiento paralelo Puede ver que Java ha estado trabajando duro para proporcionar un procesamiento paralelo más conveniente.

Supongo que te gusta

Origin blog.csdn.net/qq877728715/article/details/113987179
Recomendado
Clasificación