资讯专栏INFORMATION COLUMN

模拟病毒扫描程序(Executors、ScheduledExecutorService类)

antz / 2422人阅读

摘要:类重点是那个病毒扫描程序的例子,认真看三遍。会从线程池中选择线程,并将对象提交给线程任务。比如病毒扫描,你可以使用类实现的执行器服务,每小时运行一次病毒扫描。该应用是以固定频率执行的病毒扫描程序。

Executors类

重点是那个病毒扫描程序的例子,认真看三遍。本文花了四个小时。

GitHub代码欢迎star。

小白认为学习语言最好的方式就是模仿思考别人为什么这么写。结合栗子效果更好,也能记住知识点

Executors类允许创建线程池并返回ExecutorService对象,执行器提供了将任务提交与对任务进行解耦的标准方法,除了对基本的线程生命周期提供支持外,窒息功能其还提供统计收集,应用管理及监控方面的功能。这一切都基于 生产者-消费者模式。 使用这种设计模式可以对大型并发应用程序很好的进行扩展。

使用这种服务对象,可以运行Runnable和Callable类的实力,你只需要做的是提交任务给服务对象就可以。ExecutorService会从线程池中选择线程,并将Runnable对象提交给线程任务。当任务结束时,线程并不会销毁 ,而是返回到线程池中继续执行后续的其他任务,这样可以 避免创建和销毁线程带来的额外开销

Executors类有许多静态方法可用来创建线程池:

1、newFixedTHreadPool方法能够创建固定大小的线程池。线程池中的线程将被用来处理任务请求,如果线程处于空闲状态,线程不会销毁,而是会被存放线程池中一段不确定的是将

2、newCachedThreadPool,使用该方法创建的线程池中的线程会在空闲60秒之后自动销毁,

3、newSingleThreadExecutor该方法仅创建一个线程,当任务结束后不会销毁而是用于处理其他任务。对于多个任务同时请求,则使用队列来维护所有待处理的请求。随后会顺序执行。

4、newScheduledThreadPool可以把它看作是java.util.Timer的替代品,该方法创建固定大小的线程池用来调度执行任务,并返回一个ScheduledExecutorService对象,该对象提供了若干个方法用于执行任务的调度执行。

创建线程池以进行任务调度

有时创建可在一定时间延迟后执行的线程,可以设置一个报警器在一段时间过后报警。在某些情况下,你也希望以 一定的频率或固定时间间隔反复执行线程。
比如病毒扫描,你可以使用newScheduledThreadPool类实现的执行器服务,每24小时运行一次病毒扫描。如果有多个磁盘或大容量的磁盘需要扫描,将扫描的任务分解为多个单元。让每个单元扫描某个特定的磁盘。

另一凸显此服务很实用的应用场景是新闻聚合器。聚合器从多个新闻源收集最新新闻,并将它们排列在客户端以供阅读,多个数据源获取可以并发执行,而这根据目标数据源的网络状况,花费的时间会不一样。客户端和数据源的同步会周期性地执行。如果这样的同步操作频率很高,新的同步操作和当前正在执行的操作就有可能出现重叠。在这种情况下,最好给每次任务的执行设固定的时间间隔,ScheduledExecutosService可以帮你实现这样的需求。

ScheduledExecutorService类

1、ScheduledExecutorService 类提供了名为schedule的方法用于设定任务的未来执行。schedule方法有两个重载版本:

//Creates and executes a ScheduledFuture that becomes enabled after the given delay.
 ScheduledFuture     schedule(Callable 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对象给调用方。

2、除了这个简单的延迟执行之外,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*固定周期”,依次类推,这种方法可以用于病毒扫描

3、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.
 * 演示任务调度执行
 * 需求:
 * 如何让任务以一定的频率执行。
 * 1、该应用是以固定频率执行的病毒扫描程序。
 * 2、当扫描开始时,程序弹出窗口以显示扫描进度,当磁盘上所有文件被扫描之后,任务会停止。
 * 3、每次扫描都需要不同的时间,通过让线程随机睡眠一段时间来模拟这个过程。
 * 4、扫描结束之后,状态窗口会被关闭,知道下次扫描才会弹出,
 */
public class VirusScanner {
    private static JFrame appFrame;
    private static JLabel statusString;
    private int scanNumber = 0;
    //1、调用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() {
        //2、使用线程池中的线程来解决多重并发扫描。
        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();
                }
            }
        };
        //重点:3、使用之前创建的调度器来让扫描程序以固定频率执行。
        //         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() {
                //4、取消病毒扫描任务,并关闭调度器和状态窗口
                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 result : results) {
  result.get();
}

这样就可以顺序的获取结果,但如果某个特定的任务需要长时间才能结束,那么当前的get调用会一直阻塞.在这种情况下,即使其他任务已经提前完成,也无法获取结果,为了解决这个问题,可以使用ExecutorCompletionService类,该类会检测提交给执行器的任务,通过take方法,可以一个个地获取到任务执行的结果。

待续...

文章版权归作者所有,未经允许请勿转载,若此文章存在违规行为,您可以联系管理员删除。

转载请注明本文地址:https://www.ucloud.cn/yun/68491.html

相关文章

  • java高并发系列 - 第19天:JUC中的Executor框架详解1,全面掌握java并发相关技术

    摘要:有三种状态运行关闭终止。类类,提供了一系列工厂方法用于创建线程池,返回的线程池都实现了接口。线程池的大小一旦达到最大值就会保持不变,在提交新任务,任务将会进入等待队列中等待。此线程池支持定时以及周期性执行任务的需求。 这是java高并发系列第19篇文章。 本文主要内容 介绍Executor框架相关内容 介绍Executor 介绍ExecutorService 介绍线程池ThreadP...

    icattlecoder 评论0 收藏0
  • Java 8 并发教程:线程和执行器

    摘要:在这个示例中我们使用了一个单线程线程池的。在延迟消逝后,任务将会并发执行。这是并发系列教程的第一部分。第一部分线程和执行器第二部分同步和锁第三部分原子操作和 Java 8 并发教程:线程和执行器 原文:Java 8 Concurrency Tutorial: Threads and Executors 译者:BlankKelly 来源:Java8并发教程:Threads和Execut...

    jsdt 评论0 收藏0
  • 初读《Java并发编程的艺术》-第十章:Executor框架 -10.1 Executor框架简介

    摘要:线程的启动与销毁都与本地线程同步。操作系统会调度所有线程并将它们分配给可用的。框架的成员主要成员线程池接口接口接口以及工具类。创建单个线程的接口与其实现类用于表示异步计算的结果。参考书籍并发编程的艺术方腾飞魏鹏程晓明著 在java中,直接使用线程来异步的执行任务,线程的每次创建与销毁需要一定的计算机资源开销。每个任务创建一个线程的话,当任务数量多的时候,则对应的创建销毁开销会消耗大量...

    aisuhua 评论0 收藏0
  • Java ThreadPoolExecutor 线程池

    摘要:是一个中的工具类提供工厂方法来创建不同类型的线程池从上图中也可以看出的创建线程池的方法创建出来的线程池都实现了接口常用方法有以下几个创建固定数目线程的线程池超出的线程会在队列中等待创建一个可缓存线程池如果线程池长度超过处理需要可灵活回收空闲 Executors Executors 是一个Java中的工具类. 提供工厂方法来创建不同类型的线程池. showImg(https://segm...

    CollinPeng 评论0 收藏0
  • Java 8 并发: Threads 和 Executors

    摘要:能够异步的执行任务,并且通常管理一个线程池。这样我们就不用手动的去创建线程了,线程池中的所有线程都将被重用。在之后不能再提交任务到线程池。它不使用固定大小的线程池,默认情况下是主机的可用内核数。 原文地址: Java 8 Concurrency Tutorial: Threads and Executors Java 5 初次引入了Concurrency API,并在随后的发布版本中...

    J4ck_Chan 评论0 收藏0

发表评论

0条评论

最新活动
阅读需要支付1元查看
<