Java-并发编程-02并发包-02并发工具包


1 Fork/Join

1.1 Fork/Join概念

1.1.1 Fork/Join定义

fork/join框架是把一个大任务分割成若干个小任务,最终汇总每个任务结果后得到大任务结果的框架

  1. 分割任务: 先将大任务分割成子任务,如果子任务还是很大,则继续分割,直到分割出的子任务足够小
  2. 执行任务并合并结果: 分割的子任务分别放在双端队列里,然后几个启动线程分别从双端队列里获取任务执行. 子任务执行完的结果都统一放在一个队列里,启动一个线程从队列里拿数据,然后合并这些数据

1.1.2 工作窃取(work-stealing)算法

是指某个线程从其他队列里窃取任务来执行

  • 假如需要做一个比较大的任务,可以将这个任务分割为若干互不依赖的子任务,为了减少线程间的竞争,把这些子任务分别放到不同的队列里,并为每个队列创建一个单独的线程来执行队列里的任务,线程和队列一一对应,A线程负责处理A队列里的任务.

  • 有的线程会把自己队列里的任务干完,而其他线程对应的队列里还有任务等待处理. 如果这时干完活的线程等待着,不如去帮其他线程干活,于是它就去其他线程的队列里窃取一个任务来执行.

  • 这时这个它们会访问同一个队列,所以为了减少窃取任务线程和被窃取任务线程之间的竞争,通常会使用双端队列,被窃取任务线程永远从双端队列的头部拿任务执行,而窃取任务的线程永远从双端队列的尾部拿任务执行

  • 工作窃取算法的优点是充分利用线程进行并行计算,并减少了线程间的竞争,其缺点是在某些情况下还是存在竞争,比如双端队列里只有一个任务时.并且消耗了更多的系统资源,比如创建了多个线程和多个双端队列

  • ForkJoinPool线程池,与ExecutorService区别在于ForkJoinPool使用了"工作窃取"(线程池中所有的线程都企图找到并执行提交给线程池的任务)

  • ForkJoinTask代表运行在ForkJoinPool中的任务

    • RecursiveAction 递归无返回值的ForkJoinTask
    • RecursiveTask 递归有返回值的ForkJoinTask
  • ForkJoinWorkerThread代表ForkJoinPool线程池中的一个执行任务的线程

1.2 常用方法

// ForkJoinPool.class
// 以Runtime.getRuntime().availableProcessors()的返回值作为parallelism来创建ForkJoinPool
public ForkJoinPool();
// 创建一个包含parallelism个并行线程的ForkJoinPool
public ForkJoinPool(int parallelism)

// ForkJoinTask.class
// 在当前线程运行的线程池中安排一个异步任务,即创建一个子任务
public final ForkJoinTask<V> fork();
// 当任务完成的时候返回计算结果
public final V join();
// 开始执行任务, 如果有必要,等待计算完成
public final V invoke();
public static void invokeAll(ForkJoinTask<?>... tasks);

1.4 Fork/Join的使用

1.4.1 RecursiveAction 无返回值分治框架

public void testRecursiveAction() throws InterruptedException {
    // 创建包含Runtime.getRuntime().availableProcessors()返回值作为个数的并行线程的ForkJoinPool
    ForkJoinPool forkJoinPool = new ForkJoinPool();
    // 提交可分解的PrintTask任务
    forkJoinPool.submit(new MyRecursiveAction(0, 160));
    //阻塞当前线程直到ForkJoinPool中所有的任务都执行结束
    forkJoinPool.awaitTermination(1, TimeUnit.SECONDS);
    // 关闭线程池
    forkJoinPool.shutdown();
}
/**
 * 无返回值分治框架
 * 每个任务20个, 总共160个
 */
static class MyRecursiveAction extends RecursiveAction {
    // 每个队列小任务最大数量
    public static final int THRESHOLD = 20;
    private int start;
    private int end;
    public MyRecursiveAction(int start, int end) {
        this.start = start;
        this.end = end;
    }
    protected void compute() {
        if ((end - start) < THRESHOLD) {
            for (int i = start; i < end; i++) {
                System.out.println("线程名:" + Thread.currentThread().getName() + ",执行任务编号: " + i);
            }
        } else {
            int middle = (start + end) / 2;
            new MyRecursiveAction(start, middle).fork();
            new MyRecursiveAction(middle, end).fork();
        }
    }
}

1.4.2 RecursiveTask 有返回值分治框架

public void testRecursiveTask() throws InterruptedException, ExecutionException {
    // 创建包含Runtime.getRuntime().availableProcessors()返回值作为个数的并行线程的ForkJoinPool
    ForkJoinPool forkJoinPool = new ForkJoinPool();
    ForkJoinTask<Long> submit = forkJoinPool.submit(new MyRecursiveTask(1, 1000_0000));
    System.out.println("计算出总和=" + submit.get());
    Long result = forkJoinPool.invoke(new MyRecursiveTask(1, 1000_0000));
    System.out.println("计算出总和=" + result);
    //关闭线程池
    forkJoinPool.shutdown();
}
static class MyRecursiveTask extends RecursiveTask<Long> {
    // 每个队列小任务最大数量
    public static final int THRESHOLD = 50;
    private long start;
    private long end;
    public MyRecursiveTask(long start, long end) {
        this.start = start;
        this.end = end;
    }
    protected Long compute() {
        if ((end - start) < THRESHOLD) {
            long sum = 0;
            for (long i = start; i < end; i++) {
                sum += i;
            }
            return sum;
        } else {
            long middle = (start + end) / 2;
            // 大任务分解成两个小任务
            MyRecursiveTask leftTask = new MyRecursiveTask(start, middle);
            MyRecursiveTask rightTask = new MyRecursiveTask(middle, end);
            // 并行执行两个小任务
            leftTask.fork();
            rightTask.fork();
            // 将两个小任务累加的结果合并起来
            return leftTask.join() + rightTask.join();
        }
    }
}

1.4.3 Lambda实现累加

public void testJdk8Method(){
    // 并行流
    Instant start = Instant.now();
    long result1 = LongStream.rangeClosed(1, 1000_0000).parallel().reduce(0, Long::sum);
    Instant end = Instant.now();
    System.out.println("并行流 耗费时间: " + Duration.between(start,end).toMillis());

    // 顺序流
    start = Instant.now();
    long result2 = LongStream.rangeClosed(1,1000_0000).sequential().reduce(0,Long::sum);
    end = Instant.now();
    System.out.println("顺序流 耗费时间: " + Duration.between(start,end).toMillis());

}

2 CountDownLatch

CountDownLatch cd = new CountDownLatch(10);
cd.countDown();//每个线程执行
cd.await();主线程执行

3 CyclicBarrier

  • 可以实现线程间的计数等待,且可以重复使用
  • CountDownLatch与CyclicBarrier区别
    • CountDownLatch放行由第三者控制,CyclicBarrier放行由线程本身控制
    • CountDownLatch放行条件是>=线程数,CyclicBarrier放行条件=线程数

4 Semaphore

信号量Semaphore是对锁的扩展,Synchronized和ReentrantLock一次都只允许一个线程访问一个资源,但是信号量可以指定多个线程同时访问某个资源.

4.1 Semaphore主要方法

// 尝试获取一个准入许可,无法获得则等待,直到线程释放一个许可或当前线程被中断.
acquire()
// acquire()方法类似,但不响应中断
acquireUninterruptibly()
// 方法尝试获得一个许可,成功返回true,失败返回false,不进行等待,会立即返回
tryAcquire()
// 方法用于在线程访问资源结束后释放一个许可,以使其他等待许可的线程可以进行资源访问
release()

4.2 Semaphore的使用

public Semaphore(int permits)
public Semaphore(int permits,boolean fair)

5 Exchanger

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值