多线程相关
实现多线程编程的几种方式:
1.实现Runable接口
2.继承Thread类
3.使用Executor框架(JDK1.5之后)
这里着重介绍一下Executor框架.
一. Executor框架的两级调度模型
在HotSpot VM的线程模型中,Java线程被一对一映射为本地操作系统线程。Java线程启动时会创建一个本地操作系统线程;当Java线程终止时,这个操作系统线程也会被回收。操作系统会调用所有线程并将他们分配给可用的CPU。
可以将此种模式分为两层,在上层,Java多线程程序通常把应用程序分解为若干任务,然后使用用户级的调度器(Executor框架)讲这些任务映射为固定数量的线程;在底层,操作系统内核将这些线程映射到硬件处理器上。
两级调度模型的示意图:
简而言之,Executor框架实现了工作单元与执行单元的分离。工作单元包括Runnable 和 Callable,而执行单元由Executor框架提供。
Runnable和Callable区别(具体代码下面会提到)
(1)Callable规定的方法是call(),Runnable规定的方法是run()。其中Runnable可以提交给Thread来包装下,直接启动一个线程来执行,而Callable则一般都是提交给ExecuteService来执行。
(2)Callable的任务执行后可返回值,而Runnable的任务是不能返回值得
(3)call方法可以抛出异常,run方法不可以
(4)运行Callable任务可以拿到一个Future对象,c表示异步计算的结果。
二. Executor框架的结构
Executor主要由三部分组成:任务产生部分,任务处理部分,结果获取部分。(设计模式:生产者与消费者模式)
2.1 任务的产生:Runnable接口和Callable接口
主线程首先要创建实现 Runnable接口或者Callable接口的任务对象.工具类Executors可以把一个Runnable对象封装为一个Callable对象。当把runnble包装成callable时就存在如何处理返回值的问题,一种就是默认返回null,一种就是让程序员自定义。后者的话如果想得到正确的处理结果,传入的result就需要同时在runnable里引用并修改,不然没有卵用。
2.2 任务的执行
包括任务执行机制的核心接口Executor,以及继承自Executor的ExecutorService接口。Executor框架有两个关键类实现了ExecutorService接口:ThreadPoolExecutor 和 ScheduledThreadPoolExecutor.
2.3 异步计算的结果
包括Future和实现Future接口的FutureTask类。
Executor框架的类与接口
- Executor是一个接口,他是Executor框架的基础,它将任务的提交与任务的执行分离。
- ThreadPoolExecutor是线程池的核心实现类,用来执行被提交的任务。
- ScheduledThreadPoolExecutor是一个实现类,可以在给定的延迟后运行命令,或者定期执行命令。ScheduledThreadPoolExecutor 比 Timer 更灵活,功能更强大。
- Future接口和它的实现FutureTask类,代表异步计算的结果。
- Runnable和Callable接口的实现类,都可以被ThreadPoolExecutor 或 ScheduledThreadPoolExecutor 执行。
下面一个简单的使用ThreadPoolExecutor execute/submit 方法提交任务
ExecutorService executorService = new ThreadPoolExecutor(5, 5, 0L, TimeUnit.MILLISECONDS, new LinkedBlockingQueue<Runnable>()); Runnable r = new Runnable() { @Override public void run() { } }; Callable callable = new Callable<String>(){ @Override public String call() throws Exception { return "test"; } }; //callable可将runnable对象转为callable对象 Callable<Object> callable_r = Executors.callable(r); //ExecutorService execute 没有返回值 executorService.execute(r); //ExecutorService submit Future<?> future_r = executorService.submit(r); Future<?> future_c = executorService.submit(r);
ThreadPoolExecutor浅析
public ThreadPoolExecutor(int corePoolSize,
int maximumPoolSize,
long keepAliveTime,
TimeUnit unit,
BlockingQueue<Runnable> workQueue,
ThreadFactory threadFactory) {
this(corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue,
threadFactory, defaultHandler);
}
ThreadPoolExecutor 的三种类型
1.FixedThreadPool:创建固定长度的线程池,每次提交任务创建一个线程,直到达到线程池的最大数量,线程池的大小不再变化。这个线程池可以创建固定线程数的线程池。特点就是可以重用固定数量线程的线程池。
public static ExecutorService newFixedThreadPool(int nThreads) { return new ThreadPoolExecutor(nThreads, nThreads, 0L, TimeUnit.MILLISECONDS, new LinkedBlockingQueue<Runnable>()); }
- FixedThreadPool的corePoolSize和maxiumPoolSize都被设置为创建FixedThreadPool时指定的参数nThreads。
- 0L则表示当线程池中的线程数量操作核心线程的数量时,多余的线程将被立即停止
- 最后一个参数表示FixedThreadPool使用了无界队列LinkedBlockingQueue作为线程池的做工队列,由于是无界的,当线程池的线程数达到corePoolSize后,新任务将在无界队列中等待,因此线程池的线程数量不会超过corePoolSize,同时maxiumPoolSize也就变成了一个无效的参数,并且运行中的线程池并不会拒绝任务。
执行过程如下:
1.如果当前工作中的线程数量少于corePool的数量,就创建新的线程来执行任务。
2.当线程池的工作中的线程数量达到了corePool,则将任务加入LinkedBlockingQueue。
3.线程执行完1中的任务后会从队列中去任务。
注意LinkedBlockingQueue是无界队列,所以可以一直添加新任务到线程池。
2.SingleThreadExecutor:SingleThreadExecutor是使用单个worker线程的Executor。特点是使用单个工作线程执行任务。
public static ExecutorService newSingleThreadExecutor() { return new FinalizableDelegatedExecutorService (new ThreadPoolExecutor(1, 1, 0L, TimeUnit.MILLISECONDS, new LinkedBlockingQueue<Runnable>())); }
SingleThreadExecutor的corePoolSize和maxiumPoolSize都被设置1。
其他参数均与FixedThreadPool相同,其运行图如下:
执行过程如下:
1.如果当前工作中的线程数量少于corePool的数量,就创建一个新的线程来执行任务。
2.当线程池的工作中的线程数量达到了corePool,则将任务加入LinkedBlockingQueue。
3.线程执行完1中的任务后会从队列中去任务。
注意:由于在线程池中只有一个工作线程,所以任务可以按照添加顺序执行
3.CachedThreadPool: CachedThreadPool是一个”无限“容量的线程池,它会根据需要创建新线程。特点是可以根据需要来创建新的线程执行任务,没有特定的corePool。
public static ExecutorService newCachedThreadPool() { return new ThreadPoolExecutor(0, Integer.MAX_VALUE, 60L, TimeUnit.SECONDS, new SynchronousQueue<Runnable>()); }
CachedThreadPool的corePoolSize被设置为0,即corePool为空;maximumPoolSize被设置为Integer.MAX_VALUE,即maximum是无界的。这里keepAliveTime设置为60秒,意味着空闲的线程最多可以等待任务60秒,否则将被回收。
可以使用Executors创建上述3种类型
//FixedThreadPool Executors.newFixedThreadPool(10); //SingleThreadExecutor Executors.newSingleThreadExecutor(); //CachedThreadPool Executors.newCachedThreadPool();
使用ExecutorCompletionService获取执行结果
对于Callable对象与Runnable最大的不同就是有返回值,这里可以使用ExecutorCompletionService获取线程执行结果
public static void testExecutor() throws Exception{ ExecutorService pool = Executors.newCachedThreadPool(); CompletionService<Integer> cService = new ExecutorCompletionService<Integer>(pool); for (int i = 0; i <5 ; i++) { final int j = i;
//使用submit提交任务; cService.submit(new Callable<Integer>() { @Override public Integer call() throws Exception { Thread.sleep(1000); return j; } }); } for (int i = 0; i <5 ; i++) {
//获取线程返回结果 System.out.println(cService.take().get()); } } public static void main(String[] args) throws Exception { // ExecutorTest(); //testCallable(); //testPoolSubmit_ExecutorsCallable(); //testCallable_(); testExecutor(); }
返回结果:
2
1
4
3
0
还有一种方式是通过自己维护一个list来获取线程返回值;
这两者最主要的区别在于submit的task不一定是按照加入自己维护的list顺序完成的,也就是从list取出的Future不一定已经执行完了,需要做额外的判断;
从list中遍历的每个Future对象并不一定处于完成状态,这时调用get()方法就会被阻塞住,如果系统是设计成每个线程完成后就能根据其结果继续做后面的事,这样对于处于list后面的但是先完成的线程就会增加了额外的等待时间。
而CompletionService的实现是维护一个保存Future对象的BlockingQueue。只有当这个Future对象状态是结束的时候,才会加入到这个Queue中,take()方法其实就是Producer-Consumer中的Consumer。它会从Queue中取出Future对象,如果Queue是空的,就会阻塞在那里,直到有完成的Future对象加入到Queue中。
所以,先完成的必定先被取出。这样就减少了不必要的等待时间