Java并发编程一:并发基础必知
Java并发编程二:Java中线程
Java并发编程三:volatile使用
Java并发编程四:synchronized和lock
Java并发编程五:Atomic原子类
Java并发编程六:并发队列
Java并发编程七:ReentrantReadWriteLock和StampedLock
Java并发编程八:CountDownLatch、CyclicBarrier、Semaphore和Exchanger
在实际开发中,会使用线程池来管理线程,使用线程池能够带来3个好处:
- 降低资源消耗。通过重复利用已创建的线程来降低线程的创建和销毁带来的额外消耗。
- 提高响应速度。当任务到达时,不需要等待线程的创建就能立即执行。
- 提高线程的可管理性。对线程进行统一管理分配和调优。
实现原理
当提交一个任务到线程池时,线程池会做出以下操作。
- 判断线程池中的核心线程是否都在工作。如果不是,创建一个新的线程来执行,如果是则执行下一步。
- 判断线程池中的队列是否已满,如果不满,则将任务放入队列中,等待核心线程执行完再来从队列中取出任务,如果满了,执行下一步。
- 判断线程池中最大线程数是否已满,如果不满,则创建一个非核心的线程来运行任务,如果满了,则交给饱和策略来处理这个任务。
线程池的创建
通过ThreadPoolExecutor来创建线程池。
new ThreadPoolExecutor(int corePoolSize,
int maximumPoolSize,
long keepAliveTime,
TimeUnit unit,
BlockingQueue<Runnable> workQueue,
ThreadFactory threadFactory,
RejectedExecutionHandler handler)
- corePoolSize(核心线程数):线程池默认创建多少个核心线程数,不会销毁。
- maximumPoolSize(最大线程数):线程池中线程最大数量。
- keepAliveTime(线程存活时间):非核心线程运行完后的存活时间。
- unit(线程存活时间单位):非核心线程存活时间单位,可选天、时、分、秒、毫秒、微秒、纳秒。
- workQueue(任务队列):保存等待被执行的任务。(ps:可查看并发编程六熟悉队列)。
- threadFactory(线程工厂):给线程设置一个名字,方便后期查看。
- handler(饱和策略):当队列和最多线程数都满了,再提交任务执行饱和策略。Jdk为我们提供了4种策略。1)AbortPolicy直接抛出异常(默认)。2)CallerRunsPolicy只用调用者所在线程来运行任务。(不抛异常、不丢弃任务,吞吐量下降)。3)DiscardOldestPolicy丢弃队列里最近的一个任务,并执行当前任务。4)DiscardPolicy:不处理,丢弃掉。当然也可以实现RejectedExecutionHandler接口自定义策略。
线程池的使用
有两种方法向线程池提交任务。execute()和submit()。两者区别:
execute
execute()用于执行不需要返回值的任务,无法判断执行是否成功。
threadsPool.execute(new Runnable() { @Override public void run() { // TODO Auto-generated method stub } });
submit
submit()用于执行需要返回值的任务,线程池会返回一个future对象,通过这个对象可以判断是否执行完毕,调用对象get方法获取返回值。
Future<Object> future = threadsPool.submit(带返回值的线程);
try {
Object s = future.get();
} catch (InterruptedException e) {
// 处理中断异常
} catch (ExecutionException e) {
// 处理无法执行任务异常
} finally {
// 关闭线程池
executor.shutdown();
}
线程池的关闭
线程池提供了两种关闭方式:shutdown()和shutdownNow()。区别在于,前者会等待线程执行完毕关闭线程, 后者不需要等待线程执行完毕。
线程池的配置
合理的配置线程池,可以高效的处理任务,突出计算机多核的计算,根据不同的任务类别选择不同的配置,任务类别可分为:CPU密集型、IO密集型和混合型任务。(n为CPU个数,通过Runtime.getRuntime().availableProcessors()方法获得当前设备的CPU个数)
任务类别 | 说明 |
---|---|
CPU密集型 | 线程池的个数尽量少。如配置n+1个线程 |
IO密集型 | 由于IO操作时CPU处于闲置状态,可以多配置。如2n+1 |
混合型 | 可以拆分成CPU任务和IO任务,当这两种执行效率时间相差小,可以拆分,如果时间相差大,没必要进行拆分。 |