重点是那个病毒扫描程序的例子,认真看三遍。本文花了四个小时。java
GitHub代码欢迎star。git
小白认为学习语言最好的方式就是模仿、思考别人为何这么写。结合栗子效果更好,也能记住知识点。github
Executors类容许建立线程池并返回ExecutorService
对象,执行器提供了将任务提交与对任务进行解耦的标准方法,除了对基本的线程生命周期提供支持外,窒息功能其还提供统计收集,应用管理及监控方面的功能。这一切都基于 生产者-消费者模式。 使用这种设计模式能够对大型并发应用程序很好的进行扩展。segmentfault
使用这种服务对象,能够运行Runnable和Callable
类的实力,你只须要作的是提交任务给服务对象就能够。ExecutorService
会从线程池中选择线程,并将Runnable对象提交给线程任务。当任务结束时,线程并不会销毁 ,而是返回到线程池中继续执行后续的其余任务,这样能够 避免建立和销毁线程带来的额外开销设计模式
一、newFixedTHreadPool
方法可以建立固定大小的线程池。线程池中的线程将被用来处理任务请求,若是线程处于空闲状态,线程不会销毁,而是会被存放线程池中一段不肯定的是将网络
二、newCachedThreadPool
,使用该方法建立的线程池中的线程会在空闲60秒以后自动销毁,架构
三、newSingleThreadExecutor
该方法仅建立一个线程,当任务结束后不会销毁而是用于处理其余任务。对于多个任务同时请求,则使用队列来维护全部待处理的请求。随后会顺序执行。并发
四、newScheduledThreadPool
能够把它看做是java.util.Timer的替代品,该方法建立固定大小的线程池用来调度执行任务,并返回一个ScheduledExecutorService
对象,该对象提供了若干个方法用于执行任务的调度执行。app
有时建立可在必定时间延迟后执行的线程,能够设置一个报警器在一段时间事后报警。在某些状况下,你也但愿以 必定的频率或固定时间间隔反复执行线程。
好比病毒扫描,你可使用newScheduledThreadPool
类实现的执行器服务,每24小时运行一次病毒扫描。若是有多个磁盘或大容量的磁盘须要扫描,将扫描的任务分解为多个单元。让每一个单元扫描某个特定的磁盘。dom
另外一凸显此服务很实用的应用场景是新闻聚合器。聚合器从多个新闻源收集最新新闻,并将它们排列在客户端以供阅读,多个数据源获取能够并发执行,而这根据目标数据源的网络情况,花费的时间会不同。客户端和数据源的同步会周期性地执行。若是这样的同步操做频率很高,新的同步操做和当前正在执行的操做就有可能出现重叠。在这种状况下,最好给每次任务的执行设固定的时间间隔,ScheduledExecutosService
能够帮你实现这样的需求。
一、ScheduledExecutorService
类提供了名为schedule的方法用于设定任务的将来执行。schedule方法有两个重载版本:
//Creates and executes a ScheduledFuture that becomes enabled after the given delay. <V> ScheduledFuture<V> schedule(Callable<V> callable, long delay, TimeUnit unit) //Creates and executes a one-shot action that becomes enabled after the given delay. ScheduledFuture<?> schedule(Runnable command, long delay, TimeUnit unit)
schedule方法接收三个参数:Callable和Runnable接口、延迟时间以及时间单位。该方法安排由 Callable和Runnable
指定的任务在给定的延迟时间后执行。时间单位 由该方法的第三个参数指定。方法会返回一个Future对象给调用方。
二、除了这个简单的延迟执行以外,ScheduledExecutorService类还提供了scheduleAtFixedRate方法,该任务能够指定任务按照必定的频率执行。
//Creates and executes a periodic action that becomes enabled first after the given initial delay, and subsequently with the given period; //that is executions will commence after initialDelay then initialDelay+period, then initialDelay + 2 * period, and so on. ScheduledFuture<?> scheduleAtFixedRate(Runnable command, long initialDelay, long period, TimeUnit unit)
第一次执行发生在给定的延迟以后,后续执行发生在“延迟+固定时间”,“延迟+2*固定周期”,依次类推,这种方法能够用于病毒扫描
三、scheduleWithFiedDelay
方法在给定延迟以后第一次执行任务。以后按照固定好的时间间隔执行,时间间隔递归你觉得本次任务运行到下一次任务的开始。这类调度能够用于新闻聚合应用。
//Creates and executes a periodic action that becomes enabled first after the given initial delay, and subsequently with the given delay between the termination of one execution and the commencement of the next. ScheduledFuture<?> scheduleWithFixedDelay(Runnable command, long initialDelay, long delay, TimeUnit unit)
/** * Created by guo on 2018/2/16. * 演示任务调度执行 * 需求: * 如何让任务以必定的频率执行。 * 一、该应用是以固定频率执行的病毒扫描程序。 * 二、当扫描开始时,程序弹出窗口以显示扫描进度,当磁盘上全部文件被扫描以后,任务会中止。 * 三、每次扫描都须要不一样的时间,经过让线程随机睡眠一段时间来模拟这个过程。 * 四、扫描结束以后,状态窗口会被关闭,知道下次扫描才会弹出, */ public class VirusScanner { private static JFrame appFrame; private static JLabel statusString; private int scanNumber = 0; //一、调用Executors类的newScheduledThreadPool方法来建立线程池。 private static final ScheduledExecutorService scheduler = Executors.newScheduledThreadPool(5); private static final GregorianCalendar calendar = new GregorianCalendar(); private static VirusScanner app = new VirusScanner(); /** * scanDisk方法执行实际的扫描工做 */ public void scanDisk() { //二、使用线程池中的线程来解决多重并发扫描。 final Runnable scanner = new Runnable() { @Override public void run() { try { //将状态窗口显示给用户 appFrame.setVisible(true); scanNumber++; Calendar cal = Calendar.getInstance(); //显示扫描数以及扫描开始时间,接下来,让当前线程随机睡一段时间。 DateFormat df = DateFormat.getDateTimeInstance(DateFormat.FULL, DateFormat.MEDIUM); statusString.setText(" Scan" + scanNumber + " started at" + df.format(cal.getTime())); //常数1000是用来确保窗口至少显示1秒。在实际程序中,病毒扫描代码会放在sleep语句所在的位置。 //让线程休眠是伪装病毒扫描持续一段时间, //当线程从休眠中唤醒时,咱们隐藏了窗口,这让用户感受当前一轮已经结束。 //题外话1:请卸载国产360,QQ管家,小白能够无视。须要的组件能够下载绿色版。(明明是一个开源软件,你却说那高危险。明明是https://www.github.com开头。) //题外话2:感谢 架构@奇虎360,@江湖人称小白哥。谢谢你的心意,能力没到那,你还不能成为我职业生涯的第一位贵人。骚年,加油吧,越努力,越幸运。 Thread.sleep(1000 + new Random().nextInt(10000)); } catch (InterruptedException e) { e.printStackTrace(); } } }; //重点:三、使用以前建立的调度器来让扫描程序以固定频率执行。 // a、扫描任务在最初的一秒延迟以后会以每隔15秒的频率运行 // b、调用器会返回一个Future对象,用于以后取消扫描任务。 // c、为了可以进行取消操做,建立另外一个匿名线程。 // d、如下代码全部时间单位为秒,目前只是模拟的效果。 // e、在实际应用中,病毒扫描应当天天或每几小时执行一次 final ScheduledFuture<?> scanManager = scheduler.scheduleAtFixedRate(scanner, 1, 15, TimeUnit.SECONDS); /** * 匿名线程 * 这个线程只在60秒延迟以后运行一次,模拟会以一分钟的总时间周期执行 * 每隔15秒,病毒扫描状态窗口会弹出,而且显示请留1秒,或更长时间。 */ scheduler.schedule(new Runnable() { @Override public void run() { //四、取消病毒扫描任务,并关闭调度器和状态窗口 scanManager.cancel(true); scheduler.shutdown(); appFrame.dispose(); } }, 60, TimeUnit.SECONDS); } }
/** * 不是重点的main方法: * 建立状态窗口、设置并调用scanDisk方法。 * 注意:主线程会在以后马上结束,而在scanDisk方法中建立的线程会在接下来一分钟内继续运行。 */ public static void main(String[] args) { appFrame = new JFrame(); Dimension dimension = Toolkit.getDefaultToolkit().getScreenSize(); appFrame.setSize(400, 70); appFrame.setLocation(dimension.width / 2 - appFrame.getWidth() / 2, dimension.height / 2 - appFrame.getHeight() / 2); statusString = new JLabel(); appFrame.add(statusString); appFrame.setVisible(false); app.scanDisk(); }
以前已经学了如何将任务提交给执行器当即执行、延迟以及周期性的运行 (计算年销售额) 还了解到执行器能够提供并维护多个线程并发的执行任务 (模拟可取消任务的股票交易处理程序) 。在某些状况下,当提交多个任务给执行器,你可能但愿处理任意以结束任务的结果,而不像等到每一个任务都执行结束。目前只用过执行器的get方法会等待任务结束。当任务提交时,能够建立循环来获取每一个计算结果,代码以下:
for(Future<T> result : results) { result.get(); }
这样就能够顺序的获取结果,但若是某个特定的任务须要长时间才能结束,那么当前的get调用会一直阻塞.在这种状况下,即便其余任务已经提早完成,也没法获取结果,为了解决这个问题,可使用ExecutorCompletionService
类,该类会检测提交给执行器的任务,经过take方法,能够一个个地获取到任务执行的结果。
待续...