- 昨天学习了Java类库提供的容器类别,容器常用接口和多线程编程的相关知识。
- 今天趁热打铁,学习使用Java内置线程池、线程安全的容器、lambda表达式和Java泛型编程。Java提供的这些封装好的常用工具,无疑给开发人员提供了极大的便利。
- 回想起之前用C++实现web服务器,里面处理并发请求时,用到的线程池和阻塞队列,都是自己实现的。不过处于学习阶段,自行造轮子有利于理解底层的原理,也不算做无用功吧。
- lambda表达式和泛型编程也同样在C++中学习并使用过,这里主要学习在Java中的使用语法。
- 另外还了解了Java的反射机制,C++中没有这种机制,而这是框架设计的灵魂(所以Java的框架那么牛x?)
Java线程池
-
Java线程池顶级接口是
java.util.concurrent.Executor
,但是严格意义上Executor
并不是一个线程池,而只是一个执行线程的工具。真正的线程池接口是java.util.concurrent.ExecutorService
-
java.util.concurrent.Executor
线程工厂类提供了一些静态工厂方法,用于生成一些常用的线程池,这些工厂方法主要有static ThreadFactory defaultThreadFactory()
:返回用于创建新线程的默认线程工厂。static ExecutorService newCachedThreadPool()
:创建一个根据需要创建新线程的线程池,但在可用时将重新使用以前构造的线程。static ExecutorService newCachedThreadPool(ThreadFactory threadFactory)
:创建一个根据需要创建新线程的线程池,但在可用时将重新使用以前构造的线程,并在需要时使用提供的ThreadFactory创建新线程。static ExecutorService newFixedThreadPool(int nThreads)
:创建一个线程池,该线程池重用固定数量的从共享无界队列中运行的线程。static ExecutorService newFixedThreadPool(int nThreads, ThreadFactory threadFactory)
:创建一个线程池,重用固定数量的线程,从共享无界队列中运行,使用提供的ThreadFactory在需要时创建新线程。static ScheduledExecutorService newScheduledThreadPool(int corePoolSize)
:创建一个线程池,可以调度命令在给定的延迟之后运行,或定期执行。static ScheduledExecutorService newScheduledThreadPool(int corePoolSize, ThreadFactory threadFactory)
:创建一个线程池,可以调度命令在给定的延迟之后运行,或定期执行。static ExecutorService newSingleThreadExecutor()
:创建一个使用从无界队列运行的单个工作线程的执行程序。static ExecutorService newSingleThreadExecutor(ThreadFactory threadFactory)
:创建一个使用单个工作线程运行无界队列的执行程序,并在需要时使用提供的ThreadFactory创建一个新线程。static ScheduledExecutorService newSingleThreadScheduledExecutor()
:创建一个单线程执行器,可以调度命令在给定的延迟之后运行,或定期执行。static ScheduledExecutorService newSingleThreadScheduledExecutor(ThreadFactory threadFactory)
:创建一个单线程执行器,可以调度命令在给定的延迟之后运行,或定期执行。static ExecutorService newWorkStealingPool()
:创建使用所有 available processors作为其目标并行级别的工作窃取线程池。static ExecutorService newWorkStealingPool(int parallelism)
:创建一个维护足够的线程以支持给定的并行级别的线程池,并且可以使用多个队列来减少争用。
-
Callable和Future
- Runnable封装一个异步运行的任务,可以把它想象成一个没有参数和返回值的异步方法。而Callable和Runnable类似,但是有返回值。Callable接口是一个参数化的模型,只有一个方法Call
类型参数是返回值的类型。如,Callable<Integer>表示一个最终返回Integer对象的异步计算public interface Callable{ V call () throws Exception; }
- Future保存异步计算的结果。可以启动一个计算,将Future对象交给某个线程,然后忘掉它。这个Future对象的所有者在计算号之后就可以获得结果。Future接口有以下方法
boolean cancel(boolean mayInterruptIfRunning)
:取消执行此任务,若任务未开始则此任务不再执行。V get()
:等待计算完成,然后获得其结果,方法会阻塞直到结果可用。V get(long timeout, TimeUnit unit)
:等待计算完成,然后获得其结果,方法会阻塞直到结果可用或者超时。boolean isCancelled()
:如果此任务在正常完成之前被取消,则返回 true 。boolean isDone()
:如果任务已完成返回 true。
- 执行Callable的一种方法是使用FutureTask,它实现了Future和Runnable接口,所以可以构造一个线程来运行这个任务:
Callable<Integer> task = ...; var ft = new FutureTask<Integer>(task); new Thread(ft).start(); Integer result = ft.get(); // 结果可能在未来,阻塞等待
- Runnable封装一个异步运行的任务,可以把它想象成一个没有参数和返回值的异步方法。而Callable和Runnable类似,但是有返回值。Callable接口是一个参数化的模型,只有一个方法Call
-
通过调用上述线程池构造方法可以创建一个
ExecutorService
线程池对象,该对象主要方法如下- 提交指定的Runnable或Callable对象到
ExecutorService
对象来执行Future<T> submit(Callable<T> task)
Future<?> submit(Runnable task)
Future<T> submit(Runnable task, T result)
- 关闭服务,完成已经提交的任务但不再接受新的任务
void shutdown()
- 提交指定的Runnable或Callable对象到
-
使用线程池的步骤
- 调用
Executor
类的静态工厂方法newCacheThreadPool
或newFixedThreadPool
创建线程池ExecutorService
对象 - 新建一个对象,实现
Runnable
或者Callable
接口,重写run
方法,设置线程任务 - 调用
ExecutorService
对象的submit
提交Runnable
或者Callable
对象 - 保存好返回的
Future
对象,以便得到结果或者取消任务 - 当不需要再提交任何任务时,调用
shutdown
- 调用
Java线程安全集合
- Java类库提供了一些线程安全的集合,以供多线程环境下,并发修改一个数据结构的需要。而在C++中,STL容器库都不是线程安全的,需要自行加锁实现对容器操作的同步
- Java类库提供的线程安全集合主要有阻塞队列接口,高效的映射、集和队列接口(无锁实现)
- 实现阻塞队列接口(
Interface BlockingQueue<E>
)的子类主要有ArrayBlockingQueue<E>
:实现一个有指定容量和公平性设置的阻塞队列,队列底层实现为一个循环数组LinkedBlockingDeque<E>
:实现一个阻塞双向队列,可以构造为指定容量或容量无上限的阻塞双向队列,底层实现为双向链表LinkedBlockingQueue<E>
:实现一个阻塞队列,可以构造为指定容量或容量无上限的阻塞队列,底层实现为双向链表PriorityBlockingQueue<E>
:实现一个容量无上限的阻塞有限队列,底层实现为一个堆,可以通过指定比较器构造堆
- 线程安全的高效映射集合
ConcurrentHashMap<k, v>
: