4、线程池Thread Pool
线程虽然不像进程需要那么多资源,但是它的创建也是有一定开销的,频繁地创建和销毁线程会降低程序的性能;此外应用程序可以创建线程的数量是受机器物理条件制约的,过多的线程会耗尽机器的资源,因此我们在设计程序的时候需要限制并发线程的数量。解决这两个问题的通常做法是使用线程池。线程池在启动的时候一次性初始化若干个线程(也可以根据负载按需启动,也有闲置一定时间的线程会被销毁的策略),然后程序把任务交给线程池去执行而不是直接交给某个线程执行,由线程池给这些任务分配线程。当某个线程执行完一个任务后,线程池会把它设成空闲状态以备下一个任务重用而不是销毁它。线程池在初始化的时候需要指定线程数量上限,当并发任务数量超过线程数量的时候,线程池不会再创建新的线程而是让新任务等待,这样我们就不在需要担心线程数量过多耗尽系统资源了。JDK1.5开始为我们提供了标准的线程池。
4.1 执行器Executor
Java的线程池实现了以下Executor接口:
public interface Executor { void execute(Runnable command); } |
在多线程编程中,执行器是一种常用的设计模式,它的好处在于提供了一种简单有效的编程模型,我们只需把需要并发处理的工作拆分成独立的任务,然后交给执行器去执行即可而不必关心线程的创建,分配和调度。J2SE5.0主要提供了两种功能的执行器:ThreadPoolExecutor和ScheduledThreadPoolExecutor。ThreadPoolExecutor是基本的线程池实现,ScheduledThreadPoolExecutor在前者基础上增加了任务调度的功能,在把任务交给它时我们可以指定任务的执行时间,而不是立刻执行。
java.util.concurrent.Executors是用来创建线程池的工厂类,通过它提供的工厂方法,我们可以方便地创建不同特性的线程池。
4.2 Future接口
Executor接口并没有看起来那么理想,有时候我们执行一个任务是要得到计算的结果,有时候我们需要对任务有更多控制,例如知道它是否完成,或者中途终止它。返回void的execute方法并不能满足我们这些需求。当然我们可以在传入的Runnable类上下功夫来提供类似的功能,但是这样做繁琐且容易出错。既然J2SE为我们提供了线程池的标准实现把我们从多线程编程中解放出来,这些常见的需求当然也会很好地满足。事实上线程池实现了一个更为丰富的ExecutorService接口,它定义了执行任务并返回代表该任务的Future对象的submit方法。
通过Future接口,我们可以查看已经被提交给线程池执行的任务是否完成,获取执行的结果或者终止任务。
4.3 Runnable 和Callable 接口
实现了Runnable或Callable接口的类都可以作为任务提交给线程池执行,这两个接口的主要区别在于Callable的call方法有结果返回并且可以抛出异常而Runnable的run方法返回void且不允许有可检查的异常抛出(只能抛runtime exception)。因此如果我们的任务执行后有结果返回,应该使用Callable接口。
5、线程和集合类
5.1 线程安全的集合类
● java.util.Vector
● java.util.Stack
● java.util.HashTable
● java.util.concurrent.ConcurrentHashMap
● java.util.concurrent.CopyOnWriteArrayList
● java.util.concurrent.CopyOnWriteArraySet
● java.util.concurrent.ConcurrentLinkedQueue