Java使用线程完成异步任务是很普遍的事,而线程的创建与销毁需要一定的开销,如果每个任务都需要创建一个线程将会消耗大量的计算资源,JDK 5之后把工作单元和执行机制区分开了,工作单元包括Runnable和Callable,而执行机制则由Executor框架提供。Executor框架为线程的启动、执行和关闭提供了便利,底层使用线程池实现。使用Executor框架管理线程的好处在于简化管理、提高效率,还能避免this逃逸问题——是指不完整的对象被线程调用。
Executor框架使用了两级调度模型进行线程的调度。在上层,Java多线程程序通常把应用分解为多个任务,然后使用用户调度框架Executor将这些任务映射为固定数量的线程;在底层,操作系统内核将这些线程映射到硬件处理器上。
Executor框架包括线程池,Executor,Executors,ExecutorService,CompletionService,Future,C
allable 等。
主线程首先通过Runnable或者Callable接口创建任务对象。工具类Executors可以把一个Runnable对象封装为Callable对象(通过调用Executors.callable(Runnable task)实现),然后可以把Runnable对象直接交给ExecutorService执行,ExecutorService通过调用ExecutorService.execute(Runnable command)完成任务的执行;或者把Runnable对象或Callable对象交给ExecutorService执行,ExecutorService通过调用ExecutorService.submit(Runnable task)或者ExecutorService.submit(Callable task)完成任务的提交。在使用ExecutorService的submit方法的时候会返回一个实现Future接口的对象(目前返回的是FutureTask对象)。由于FutureTask实现了Runnable,也可以直接创建FutureTask,然后交给ExecutorService执行。
ExecutorService 接口继承自 Executor 接口,它提供了更丰富的实现多线程的方法。比如可以调用 ExecutorService 的 shutdown()方法来平滑地关闭 ExecutorService,调用该方法后,将导致 ExecutorService 停止接受任何新的任务且等待已经提交的任务执行完成(已经提交的任务会分两类:一类是已经在执行的,另一类是还没有开始执行的),当所有已经提交的任务执行完毕后将会关闭 ExecutorService。
通过Executors工具类可以创建不同的线程池ThreadPoolExecutor:SingleThreadExecutor、FixedThreadPool和CachedThreadPool。
FixedThreadPool
public static ExecutorService newFixedThreadPool(int nThreads)public static ExecutorService newFixedThreadPool(int nThreads,ThreadFactory factory)
- 1
- 2
FixedThreadPool适用于为了满足管理资源的需求,而需要限制当前线程数量的应用场景,它适用于负载比较重的服务器。
SingleThreadExecutor
public static ExecutorService newSingleThreadExecutor()public static ExecutorService newSingleThreadExecutor(ThreadFactory factory)
- 1
- 2
SingleThreadExecutor适用于需要保证顺序地执行各个任务,并且在任意时间点不会有多个线程在活动的场景。
CachedThreadPool
public static ExecutorService newCachedThreadPool()public static ExecutorService newCachedThreadPool(ThreadFactory factory)
- 1
- 2
CachedThreadPool是大小无界的线程池,适用于执行很多的短期异步任务的小程序,或者负载比较轻的服务器。
ScheduledThreadPoolExecutor
public static ScheduledExecutorService newScheduledThreadPool(int corePoolSize)public static ScheduledExecutorService newScheduledThreadPool(int corePoolSize,ThreadFactory factory)
- 1
- 2
创建一个支持定时及周期性的任务执行的线程池,多数情况下可用来替代Timer类。ScheduledThreadPoolExecutor适用于需要在多个后台线程执行周期任务,同时为了满足资源管理需求需要限制后台线程数量的应用场景。
Executor框架的最核心的类是ThreadPoolExecutor,它是线程池的实现类,主要由四个组件构成。
- corePool:核心线程池的大小
- maximumPool:最大线程池的大小
- BlockingQueue:用来暂时保存任务的工作队列
- RejectedExecutionHandler:饱和策略。当ThreadPoolExecutor已经关闭或者ThreadPoolExecutor已经饱和时(是指达到了最大线程池的大小且工作队列已满),execute方法将要调用的Handler
使用Executor框架执行Runnable任务
package com.rhwayfun.concurrency;import java.util.concurrent.ExecutorService;import java.util.concurrent.Executors;/** * Created by rhwayfun on 16-4-4. */public class ExecutorRunnableTest { static class Runner implements Runnable{ public void run() { System.out.println(Thread.currentThread().getName() + " is called"); } } public static void main(String[] args){ ExecutorService cachedThreadPool = Executors.newCachedThreadPool(); for (int i = 0; i < 5; i++){ cachedThreadPool.execute(new Runner()); } cachedThreadPool.shutdown(); }}
结果如下:
通过下面对CachedThreadPool的分析就能知道执行任务的时候首先会从线程池选择空闲的线程执行任务,如果没有没有空闲的线程就会创建一个新的线程执行任务。这里出现同一个线程执行两遍的原因在于第一次执行任务的空闲线程执行完任务后不会马上终止,认识等待60秒才会终止。
使用Executor框架执行Callable任务
Runnable 任务没有返回值而 Callable 任务有返回值。并且 Callable 的call()方法只能通过 ExecutorService 的 submit(Callable task) 方法来执行,并且返回一个 Future(目前是FutureTask),是表示任务等待完成的 Future。如果需要得到Callable执行返回的结果,可以通过吊桶FutureTask的get方法得到。
下面的代码演示使用Executor框架执行Callable任务:
package com.rhwayfun.concurrency;import java.util.ArrayList;import java.util.List;import java.util.concurrent.*;/** * Created by rhwayfun on 16-4-4. */public class ExecutorCallableTest { /** * Callable任务 */ static class Runner implements Callable<String> { private String runId; public Runner(String runId) { this.runId = runId; } public String call() throws Exception { System.out.println(Thread.currentThread().getName() + " call method is invoked!"); return Thread.currentThread().getName() + " call method and id is " + runId; } } public static void main(String[] args) { //线程池 ExecutorService cachedThreadPool = Executors.newCachedThreadPool(); //接收Callable任务的返回结果 List<Future<String>> futureTaskList = new ArrayList<Future<String>>(); for (int i = 0; i < 5; i++) { Future<String> future = cachedThreadPool.submit(new Runner(String.valueOf(i))); futureTaskList.add(future); } //遍历线程执行的返回结果 for (Future f : futureTaskList) { try { //如果任务没有完成则忙等待 while (!f.isDone()) {} System.out.println(f.get()); } catch (InterruptedException e) { e.printStackTrace(); } catch (ExecutionException e) { e.printStackTrace(); } finally { //关闭线程池,不再接收新的任务 cachedThreadPool.shutdown(); } } }}
程序的运行结果如下:
submit 方法也是首先选择空闲线程来执行任务,如果没有,才会创建新的线程来执行任务。如果 Future 的返回尚未完成则 get()方法会阻塞等待直到 Future 完成返回。
FixedThreadPool详解
创建FixedThreadPool的源码如下:
public static ExecutorService newFixedThreadPool(int nThreads) { return new ThreadPoolExecutor(nThreads, nThreads, 0L, TimeUnit.MILLISECONDS, new LinkedBlockingQueue<Runnable>()); }
- 5
其corePoolSize和maximumPoolSize都被设为nThreads的值。当线程池中的线程数大于corePoolSize时,keepAliveTime为多余的空闲线程等待新任务的最长时间,超过这个时间后多余的线程将被终止。具体在FixedThreadPool的执行过程如下:
- 如果当前运行的线程数少于corePoolSize,就创建新的线程执行任务
- 在线程池如果当前运行的线程数等于corePoolSize时,将任务加入到LinkedBlockingQueue等待执行
- 线程执行完1中的任务后,会在循环中反复从LinkedBlockingQueue获取任务来执行
由于LinkedBlockingQueue使用的无界队列,所以线程池中线程数不会超过corePoolSize,因此不断加入线程池中的任务将被执行,因为不会马上被执行的任务都加入到LinkedBlockingQueue等待了。
CachedThreadPool详解
CachedThreadPool是一个根据需要创建线程的线程池。创建一个CachedThreadPool的源码如下:
public static ExecutorService newCachedThreadPool() { return new ThreadPoolExecutor(0, Integer.MAX_VALUE, 60L, TimeUnit.SECONDS, new SynchronousQueue<Runnable>()); }
出,CachedThreadPool的corePoolSize为0,maximumPoolSize为Integer.MAX_VALUE,keepAliveTime为60L,意味着多余的空闲线程等待新任务的执行时间为60秒。
CachedThreadPool使用没有容量的SynchronousQueue作为线程池的工作队列(SynchronousQueue是一个没有容量的阻塞队列,每个插入操作必须等待另一个线程的对应移除操作),但是CachedThreadPool的maximumPool是无界的。这就意味着如果线程的提交速度高于线程的处理速度,CachedThreadPool会不断创建线程,极端情况是因为创建线程过多耗尽CPU和内存资源。
CachedThreadPool的执行过程如下:
- 首先执行SynchronousQueue的offer方法。如果maximumPool有空闲线程正在执行SynchronousQueue.poll(keepAliveTime,TimeUnit.NANOSECONDS),那么主线程执行offer操作与空闲线程的poll操作配对成功,主线程把任务交给空闲线程执行,否则执行2
- 如果maximumPool为空或者maximumPool没有空闲线程时,CachedThreadPool将会创建一个新线程执行任务
- 在步骤2新创建的线程将任务执行完后,将会在SynchronousQueue队列中等待60秒,如果60秒内主线程提交了新任务,那么将继续执行主线程提交的新任务,否则会终止该空闲线程。
ScheduledThreadPoolExecutor详解
ScheduledThreadPoolExecutor继承自ThreadPoolExecutor,主要用来在给定的延迟之后执行任务,或者定期运行任务。Timer类也具有类似的功能,Timer对应的单个的后台线程,而ScheduledThreadPoolExecutor可以在构造函数内指定多个对应的后台线程。
ScheduledThreadPoolExecutor为了支持周期性任务的执行,使用了DelayQueue作为任务队列。ScheduledThreadPoolExecutor会把待调度的任务(该任务是ScheduledFutureTask)放到DelayQueue中,线程池中的线程从DelayQueue中获取要执行的定时任务并执行。
ScheduledFutureTask包含了3个变量:
- long型变量time,是任务具体的执行时间
- long型变量sequenceNumber,是这个任务被添加到ScheduledThreadPoolExecutor中的序号
- long型成员period,表示任务执行的间隔周期
下面是ScheduledThreadPoolExecutor具体的执行步骤:
- 线程从DelayQueue中获取已经到期的ScheduledFutureTask。到期任务是指time大于等于当前时间的任务
- 线程执行这个过期任务
- 线程修改这个任务的time变量为下次执行的时间(当前时间加上间隔时间)
- 线程把修改后的任务放回DelayQueue,过期后会被重新执行
新的改变
我们对Markdown编辑器进行了一些功能拓展与语法支持,除了标准的Markdown编辑器功能,我们增加了如下几点新功能,帮助你用它写博客:
- 全新的界面设计 ,将会带来全新的写作体验;
- 在创作中心设置你喜爱的代码高亮样式,Markdown 将代码片显示选择的高亮样式 进行展示;
- 增加了 图片拖拽 功能,你可以将本地的图片直接拖拽到编辑区域直接展示;
- 全新的 KaTeX数学公式 语法;
- 增加了支持甘特图的mermaid语法1
- 增加了 多屏幕编辑 Markdown文章功能;
- 增加了 焦点写作模式、预览模式、简洁写作模式、左右区域同步滚轮设置 等功能,功能按钮位于编辑区域与预览区域中间;
- 增加了 检查列表 功能。
功能快捷键
撤销:Ctrl/Command + Z
重做:Ctrl/Command + Y
加粗:Ctrl/Command + B
斜体:Ctrl/Command + I
标题:Ctrl/Command + Shift + H
无序列表:Ctrl/Command + Shift + U
有序列表:Ctrl/Command + Shift + O
检查列表:Ctrl/Command + Shift + C
插入代码:Ctrl/Command + Shift + K
插入链接:Ctrl/Command + Shift + L
插入图片:Ctrl/Command + Shift + G
合理的创建标题,有助于目录的生成
直接输入1次#,并按下space后,将生成1级标题。
输入2次#,并按下space后,将生成2级标题。
以此类推,我们支持6级标题。有助于使用TOC
语法后生成一个完美的目录。
如何改变文本的样式
强调文本 强调文本
加粗文本 加粗文本
标记文本
删除文本
引用文本
H2O is是液体。
210 运算结果是 1024.
插入链接与图片
链接: link.
图片:
带尺寸的图片:
当然,我们为了让用户更加便捷,我们增加了图片拖拽功能。
如何插入一段漂亮的代码片
去博客设置页面,选择一款你喜欢的代码片高亮样式,下面展示同样高亮的 代码片
.
// An highlighted block var foo = 'bar';
生成一个适合你的列表
- 项目
- 项目
- 项目
- 项目1
- 项目2
- 项目3
创建一个表格
一个简单的表格是这么创建的:
项目 | Value |
电脑 | $1600 |
手机 | $12 |
导管 | $1 |
设定内容居中、居左、居右
使用:---------:
居中
使用:----------
居左
使用----------:
居右
第一列 | 第二列 | 第三列 |
第一列文本居中 | 第二列文本居右 | 第三列文本居左 |
SmartyPants
SmartyPants将ASCII标点字符转换为“智能”印刷标点HTML实体。例如:
TYPE | ASCII | HTML |
Single backticks |
| ‘Isn’t this fun?’ |
Quotes |
| “Isn’t this fun?” |
Dashes |
| – is en-dash, — is em-dash |
创建一个自定义列表
HTML
Authors
John
Luke
如何创建一个注脚
一个具有注脚的文本。2
注释也是必不可少的
Markdown将文本转换为 HTML。
KaTeX数学公式
您可以使用渲染LaTeX数学表达式 KaTeX:
Gamma公式展示
你可以找到更多关于的信息 LaTeX 数学表达式here.
新的甘特图功能,丰富你的文章
gantt
dateFormat YYYY-MM-DD
title Adding GANTT diagram functionality to mermaid
section 现有任务
已完成 :done, des1, 2014-01-06,2014-01-08
进行中 :active, des2, 2014-01-09, 3d
计划一 : des3, after des2, 5d
计划二 : des4, after des3, 5d
- 关于 甘特图 语法,参考 这儿,
UML 图表
可以使用UML图表进行渲染。 Mermaid. 例如下面产生的一个序列图::
张三 李四 王五 你好!李四, 最近怎么样? 你最近怎么样,王五? 我很好,谢谢! 我很好,谢谢! 李四想了很长时间, 文字太长了 不适合放在一行. 打量着王五... 很好... 王五, 你怎么样? 张三 李四 王五
这将产生一个流程图。:
链接
长方形
圆
圆角长方形
菱形
- 关于 Mermaid 语法,参考 这儿,
FLowchart流程图
我们依旧会支持flowchart的流程图:
- 关于 Flowchart流程图 语法,参考 这儿.
导出与导入
导出
如果你想尝试使用此编辑器, 你可以在此篇文章任意编辑。当你完成了一篇文章的写作, 在上方工具栏找到 文章导出 ,生成一个.md文件或者.html文件进行本地保存。
导入
如果你想加载一篇你写过的.md文件或者.html文件,在上方工具栏可以选择导入功能进行对应扩展名的文件导入,
继续你的创作。