Java中的并发工具及框架

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/Mutou_ren/article/details/85136572

并发工具类

CountDownLatch

允许一个或多个线程等待其他线程完成操作,比传统的join机制更好。
其构造函数接收一个int类型的参数作为计数器(初始化后无法改变该值),即要等待的线程数量(也可以是一个线程的多个执行步骤)。每次调用countDown都会使计数器减一,await()方法会一直阻塞当前线程知道计数器归零,同时该方法也提供超时机制。

CyclicBarrier同步屏障

所有线程都会止步于一个屏障(同步点),直到最后一个线程也抵达屏障才会放行。默认构造器参数为拦截线程数,线程调用await()方法表明已经抵达屏障。同时提供重载构造器接收Runnable任务,当线程抵达屏障后会先去执行该任务。
计数器可以使用reset()重置,如果计算错误可以重新算一遍,还提供了诸如getNumberWaiting()获得阻塞线程数量,isBroken()是否被中断。

Semaphore信号量

用于控制同时访问特定资源的线程数量,用于流量控制,如数据库连接。
构造方法参数为并发数量,线程中调用semaphore.acquire()获得并行许可,使用release()释放许可

Exchanger

用于线程间的数据交换。它提供一个同步点,两个线程可以交换彼此的数据,通过exchange()方法进行数据的交换。当一个线程首先执行该方法,会等待第二个线程也执行该方法,当两线程都达到同步点时,可以将本线程生产的数据传递给对方。提供超时机制。
应用场景为:遗传算法、校对工作

并发框架

Fork/Join框架

并行执行任务框架,用于将大任务分割成若干小任务,再汇总小任务结果得到大任务结果。
设计思路

  1. 分割任务。一个fork类将大任务分割成足够小的小任务
  2. 合并结果。子任务放在双端队列中,每次添加到队首,启动几个线程进行执行,若某些线程执行速度快会进行工作窃取,从其他工作线程队列尾部获取任务执行,所有结果放在一个队列中由专门的线程合并结果。

ForkJoinTask的子类RecursiveAction用于无返回结果的任务,RecursiveTask用于有返回值的任务
ForkJoinPool用来执行ForkJoinTask

forkJoinPool.submit()执行总任务,返回一个Future结果;
countTask.fork()开始执行子任务,进入子任务的compute方法,使用countTask.join拿到子任务返回的结果

该框架使用isCompletedAbnormally()检测任务是否已经抛出异常或被取消,使用gerException()方法获取抛出的异常,若没有异常或未完成返回null

实现原理
fork()会调用ForkJoinWorkerThread.pushTask()异步执行该任务并立即返回结果,pushTask()把当前任务存放在ForkJoinTask数组队列里再调用ForkJoinPoolSignalWork()方法唤醒或新建一个工作线程执行任务。
join主要是阻塞当前线程并等待获取结果。它调用doJoin()方法得到当前任务状态来判断返回什么结果

  • 已完成:直接返回结果
  • 被取消:抛出取消异常
  • 信号
  • 出现异常:抛出对应异常

doJoin()先查看任务状态是否已经完成,完成返回状态;没执行完从任务数组里取出任务并执行。

Executor

Executor执行器提供Runnable对象的管理,使用线程池有以下三种好处:

  1. 重复利用已创建的线程,降低资源消耗
  2. 无需等待线程创建后执行任务,提高响应速度
  3. 统一分配、调优、监控,提高可管理性

提交任务给线程池时,其工作处理流程如下:

  1. 判断核心线程池是否已满,若未满,则创建线程处理任务;
  2. 若核心池已满,将任务加入到阻塞队列中;
  3. 若阻塞队列已满,无法加入,则创建新线程处理任务;
  4. 若创建新线程使线程数大于线程池最大容量,拒绝任务并抛出异常。

线程池的创建

ExecutorService exec = Executors.newCachedThreadPool();
exec.execute(new Runnable());

newCachedThreadPool创建一个可缓存线程池,如果线程池长度超过处理需要,可灵活回收空闲线程,若无可回收,则新建线程。
newFixedThreadPool创建一个定长线程池,可控制线程最大并发数,超出的线程会在队列中等待。
newScheduledThreadPool 创建一个定长线程池,支持定时及周期性任务执行。
newSingleThreadExecutor 创建一个单线程化的线程池,它只会用唯一的工作线程来执行任务,保证所有任务按照指定顺序(FIFO, LIFO, 优先级)执行。
不建议使用Executors创建线程池,可能会产生以下两个问题

  • newFixedThreadPoolnewSingleThreadExecutor队列中可能会有过多的等待处理的事物,占用很多空间,可能产生OOM
  • newCachedThreadPoolnewScheduledThreadPool线程最大数量为Integer.MAX_VALUE,可能会产生很多的线程,造成OOM

建议手动创建线程池,使用构造器

    public ThreadPoolExecutor(int corePoolSize,
                              int maximumPoolSize,
                              long keepAliveTime,
                              TimeUnit unit,
                              BlockingQueue<Runnable> workQueue,
                              ThreadFactory threadFactory,
                              RejectedExecutionHandler handler) 

在这里插入图片描述

线程池的使用

通过execute()submit()方法提交任务,其中前者提交无返回值任务,后者提交有返回值任务,见下方Callable

线程池的关闭

shtudown()shutdownNow()用来关闭线程池,实现方式是遍历线程调用interrupt,区别是前者不在接收新任务,继续执行正在执行的任务;后者不再接收新任务,终止正在执行的任务,返回等待任务列表。

线程池的监控

  1. taskCount:线程池需要执行的任务数量
  2. completedTaskCount:线程池运行过程中已完成任务数量,小于等于taskCount
  3. largestPoolSize:线程池曾经创建过的最大线程数量
  4. getPoolSize:线程池线程数量
  5. getActiveCount:活动的线程数
  6. 继承线程池自定义线程池,重写空方法beforeExecute()afterExecute()terminated方法。

Callable

Callable接口与Runnable的区别在于前者拥有返回值,实现该接口实现call()方法。该接口对象必须与线程池的executorService.submit()方法绑定:executorService.submit(new Callable()),返回一个Future对象,可以提前使用它,但只有计划任务完成后改对象才有具体的结果,若还未完成就调用get()取值使用,将进入阻塞直到计划任务完成,或者使用isDone()检查是否完成。

猜你喜欢

转载自blog.csdn.net/Mutou_ren/article/details/85136572