线程并发工具类

Fork-Join

Fork-Join体现了 分而治之 的设计思想

  • 分而治之:将一个大问题,分割成若干个相同的小问题,且各个小问题之间没有关联
原理:
  • 在必要的情况下,将一个大任务,进行拆分(fork)成若干个小任务,完成每个小任务之后,再将这若干个小任务的结果进行合并(join)汇总
示意图:

在这里插入图片描述

工作密取:

  • 所谓的工作密取,举例来说就是:假设当前大任务被拆分成了50个小任务,但是此时只有5个线程可以用来执行任务,那么也就是每个线程需要处理10个任务,那么当某一个线程较快的完成了自身的10个任务之后,它就会去其他某一个未执行完毕的线程处拿到还未处理的任务,处理完成后,将任务结果放回到对应的获取的那个线程处
工作示意图

在这里插入图片描述

  • 工作密取可以充分的利用线程的利用率,从而加快执行速度

Fork-Join标准范式

示意图:

在这里插入图片描述

  • 如果我们需要使用Fork-Join框架,必须首先创建一个Fork-Join任务。它需要提供在任务中执行 forkJoin操作机制,通常我们继承 ForkJoinTask的子类:

    1. RecursiveTask<T> :用于存在返回值的任务
    2. RecursiveAction:用于没有返回值的任务
  • Task任务需要通过ForkJoinPool来执行,使用 submit 或者 invoke来提交,两者的区别为:

    1. submit:是异步执行,调用之后对应线程可以继续执行下面的程序,而不需要等待任务完成
    2. invoke:是同步执行,调用之后需要等待任务完成后才能执行后面的程序
  • joinget方法用来获取任务执行结束后返回的结果

  • 在我们自己实现的compute方法中,首先需要判断任务是否足够小,如果足够小,则直接执行任务;如果判断任务还不够小,就必须继续拆分,分割成两个子任务,每个子任务在调用 **invokeAll()**方法时就又会进入 **compute**方法;使用 **join**方法会等待子任务执行完得到其结果。

  • 使用Fork-Join进行相关功能实现的时候,其实是将工作内容分成两个部份:

    1. Fork,即解析任务,过滤到足够小进行分步执行;
    2. Join,即将小任务执行完毕的结果整合。最终完成完整功能的实现。
代码实例:
/* 使用RecursiveTask计算一个大数组,并得到其最终计算值 */
package demo3;

import java.util.Random;

class UserArray {
    //数组长度
    public static final int ARRAY_LENGTH  = 4000;

    public static int[] makeArray() {

        //new一个随机数发生器
        Random r = new Random();
        int[] result = new int[ARRAY_LENGTH];
        for(int i=0;i<ARRAY_LENGTH;i++){
            //用随机数填充数组
            result[i] =  r.nextInt(ARRAY_LENGTH*3);
        }
        return result;

    }
}

package demo3;

import java.util.concurrent.ForkJoinPool;
import java.util.concurrent.RecursiveTask;

class SumArray extends RecursiveTask<Integer> {

    public int[] src;
    public int fromIndex;
    public int toIndex;
    public static final int STANDED = UserArray.ARRAY_LENGTH/10;

    public SumArray(int[] array, int start, int end) {
        src = array;
        fromIndex = start;
        toIndex = end;
    }

    @Override
    protected Integer compute() {
        //首先判断当前任务是否足够小
        if ((toIndex - fromIndex) < STANDED) {
            //任务已经足够小,直接进行计算
            int result = 0;
            for(int i = fromIndex; i < toIndex; i++) {
                result += src[i];
            }
            return result;
        } else {
            //任务还不够小,需要进一步拆分
            //此处采取二分法,取中间值进行拆分
            int midIndex = (fromIndex + toIndex) / 2;
            //将这个任务分割成两个子任务,即创建两个子任务
            SumArray leftSumTask = new SumArray(src, fromIndex, midIndex);
            SumArray rightSumTask = new SumArray(src, (midIndex + 1), toIndex);
            //创建了两个子任务后,将子任务通过invokeAll进行提交
            invokeAll(leftSumTask, rightSumTask);
            //提交完之后,通过join方法获取子任务的返回值
            return leftSumTask.join() + rightSumTask.join();
        }
    }

    public static void main(String[] args) {
        //创建一个需要计算的数组
        int[] Array = UserArray.makeArray();
        //创建ForkJoinPool
        ForkJoinPool pool = new ForkJoinPool();

        //创建fork-join任务
        SumArray sumTask = new SumArray(Array, 0, Array.length - 1);

        //记录一下开始时间
        long start = System.currentTimeMillis();

        //将创建的fork-join任务提交到ForkJoinPool中进行执行
        pool.invoke(sumTask);

        //通过join方法获取fork-join任务的执行结果
        int result = sumTask.join();

        //输出结果和花费时间
        System.out.println("sum result = " + result + ", spend time = " + (System.currentTimeMillis() - start));

    }
}


/* 使用RecursiveTask进行一个大数组的排序,并得到其最终排序后的数组 */
class SortArray extends RecursiveTask<int[]> {

    public int[] src;
    public int fromIndex, toIndex;
    public static final int STAND = UserArray.ARRAY_LENGTH/10;

    public SortArray(int[] array) {
        this.src = array;
        fromIndex = 0;
        toIndex = src.length - 1;
    }

    @Override
    protected int[] compute() {
        //进行各个子表的排序
        //首先进行子表大小的判断,fork中进行子表排序
        if ((toIndex - fromIndex) < STAND) {
            int i, j, temp;
            for (i = fromIndex; i < toIndex; i++) {
                for (j = i+1; j<= toIndex; j++) {
                    if (src[i] > src[j]) {
                        temp = src[i];
                        src[i] = src[j];
                        src[j] = temp;
                    }
                }
            }
            return src;
        } else {
            //子表大小不满足要求,需要继续拆分
            int midIndex = (fromIndex + toIndex)/2;
            SortArray left = new SortArray(Arrays.copyOfRange(src, fromIndex, midIndex));
            SortArray right = new SortArray(Arrays.copyOfRange(src, midIndex+1, toIndex));
            //拆分的左右子表再提交
            SortArray.invokeAll(left, right);
            //得到每个子表的排序,得到有序表
            int[] resLeft = left.join();
            int[] resRight = right.join();
            //将有序子表进行合并
            int[] result = new int[resLeft.length + resRight.length];
            for(int index = 0, i = 0, j = 0; index < result.length; index++) {
                if (i >= resLeft.length) {
                    //如果i大于左子表的长度,说明左子表存储完成,依照顺序存储右子表即可
                    result[index] = resRight[j++];
                } else if (j >= resRight.length) {
                    //如果j大于右子表的长度,说明右子表存储完成,依照顺序存储左子表即可
                    result[index] = resLeft[i++];
                } else if (resLeft[i] > resRight[j]) {
                    //如果左子表的值大于右子表的值,那么将右子表的值存入最后的result数组中
                    result[index] = resRight[j++];
                }else if (resLeft[i] <= resRight[j]) {
                    //如果右子表的值大于左子表的值,那么将左子表的值存入最后的result数组中
                    result[index] = resLeft[i++];
                }
            }
            return result;
        }
    }

    public static void main(String[] args) {
        //新建排序数组
        int[] array = UserArray.makeArray();
        //创建ForkJoinPool
        ForkJoinPool pool = new ForkJoinPool();
        
        //新建task
        SortArray sortTask = new SortArray(array);
        
        //将任务放进pool里执行
        pool.invoke(sortTask);

        //得到最终的排序后的数组
        int[] res = sortTask.join();

        for (int i = 0; i < res.length; i++) {
            System.out.println("array[ " + i + " ] = " + res[i] + "\n");
        }
    }
}
  • 如果我们使用单线程执行累加,会发现只针对该示例,单线程执行速度反而更快,这个原因是:使用Fork-Join框架,是使用递归及多线程的方式进行运算,而递归会涉及出栈入栈动作,多线程的存在则会涉及上下文切换的动作,而这都是耗时的操作,所以在数据量不大,且任务不耗时的场景下,会出现使用Fork-Join不如单线程运行的快的现象,这也说明,使用Fork-Join不代表着运行一定快;但只要当任务是耗时的,或者数据量很大的场景,使用Fork-Join运行效率和速度就能够得以体现!

CountDownLatch

CountDownLatch作用:

  • CountDownLatch 这个类能够使一个线程等待其他线程完成各自的工作后再执行

  • CountDownLatch 本质上就是通过一个计数器来实现的,计数器的初始值为初始任务的数量,每当完成一个任务,就可以调用CountDownLatch.countDown()去进行计数器减一,当计数器为0时,就表示所有的任务都已经完成了,那么在闭锁上等待CountDownLatch.await()的线程就可以恢复执行

CountDownLatch的执行示意图:

在这里插入图片描述

从这幅图中,我们可以获知三个信息:

1. CNT的数值和线程数可以不相等,CNT的数值可以远远大于线程数
2. 同一个线程可以多次执行`CountDownLatch.countDown()`去进行计数器递减的操作
3. 可以多个线程调用`CountDownLatch.await()`方法进行等待

使用CountDownLatch时,无需考虑线程安全问题,因为CountDownLatch内部使用了AQS,具体可见之后的AQS介绍文章

代码示例

public class MyClass {

  public static CountDownLatch latch;

  static class MyThread implements Runnable {

      @Override
      public void run() {
          try {
              Thread.sleep(5);
          } catch (InterruptedException e) {
              e.printStackTrace();
          }
          latch.countDown();
          System.out.println("this Thread:" + Thread.currentThread().getName() + ",latch num = " + latch.getCount());
      }
  }

  public static void main(String[] args) throws InterruptedException {
      latch = new CountDownLatch(3);
      Thread thread1 = new Thread(new MyThread());
      Thread thread2 = new Thread(new MyThread());

      thread1.start();
      thread2.start();

      System.out.println("This is Main Thread, latch count num = " + latch.getCount());
      latch.countDown();
      latch.await();
      System.out.println("main thread end............");
  }
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值