Java并发编程指南(七):定制并发类

1,定制ThreadPoolExecutor类

执行者框架(Executor framework)是一种机制,允许你将线程的创建与执行分离。它是基于Executor和ExecutorService接口及其实现这两个接口的ThreadPoolExecutor类。它有一个内部的线程池和提供允许你提交两种任务给线程池执行的方法。这些任务是:

  • Runnable接口,实现没有返回结果的任务
  • Callable接口,实现返回结果的任务
在这两种情况下,你只有提交任务给执行者。这个执行者使用线程池中的线程或创建一个新的线程来执行这些任务。执行者同样决定任务被执行的时刻。

覆写ThreadPoolExecutor类的一些方法,计算你在执行者中执行的任务的执行时间,并且将关于执行者完成它的执行的统计信息写入到控制台。

//1.创建MyExecutor类,并指定它继承ThreadPoolExecutor类。
class MyExecutor extends ThreadPoolExecutor {
    //2.声明一个私有的、ConcurrentHashMap类型的属性,并参数化为String和Date类,名为startTimes。
    private ConcurrentHashMap<String, Date> startTimes;
    //3.实现这个类的构造器,使用super关键字调用父类的构造器,并初始化startTime属性。
    public MyExecutor(int corePoolSize, int maximumPoolSize, long keepAliveTime,
                      TimeUnit unit, BlockingQueue<Runnable> workQueue) {
        super(corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue);
        startTimes = new ConcurrentHashMap<>();
    }

    //4.覆盖shutdown()方法。将关于已执行的任务,正在运行的任务和待处理的任务信息写入到控制台。然后,使用super关键字调用父类的shutdown()方法。
    @Override
    public void shutdown() {
        System.out.printf("MyExecutor: Going to shutdown.\n");
        System.out.printf("MyExecutor: Executed tasks: %d\n", getCompletedTaskCount());
        System.out.printf("MyExecutor: Running tasks:  %d\n", getActiveCount());
        System.out.printf("MyExecutor: Pending tasks:  %d\n", getQueue().size());
        super.shutdown();
    }

    //5.覆盖shutdownNow()方法。将关于已执行的任务,正在运行的任务和待处理的任务信息写入到控制台。然后,使用super关键字调用父类的shutdownNow()方法。
    @Override
    public List<Runnable> shutdownNow() {
        System.out.printf("MyExecutor: Going to immediately  shutdown.\n");
        System.out.printf("MyExecutor: Executed tasks: %d\n", getCompletedTaskCount());
        System.out.printf("MyExecutor: Running tasks:  %d\n", getActiveCount());
        System.out.printf("MyExecutor: Pending tasks:  %d\n", getQueue().size());
        return super.shutdownNow();
    }

    //6.覆盖beforeExecute()方法。写入一条信息(将要执行任务的线程名和任务的哈希编码)到控制台。在HashMap中,使用这个任务的哈希编码作为key,存储开始日期。
    @Override
    protected void beforeExecute(Thread t, Runnable r) {
        System.out.printf("MyExecutor: A task is beginning: %s : %s\n", t.getName(), r.hashCode());
        startTimes.put(String.valueOf(r.hashCode()), new Date());
    }

    //7.覆盖afterExecute()方法。将任务的结果和计算任务的运行时间(将当前时间减去存储在HashMap中的任务的开始时间)的信息写入到控制台。
    @Override
    protected void afterExecute(Runnable r, Throwable t) {
        Future<?> result = (Future<?>) r;
        try {
            System.out.printf("*********************************\n");
            System.out.printf("MyExecutor: "+ Thread.currentThread().getName()+" task is finishing.\n");
            System.out.printf("MyExecutor: Result: %s\n", result.get());
            Date startDate = startTimes.remove(String.valueOf(r.hashCode()));
            Date finishDate = new Date();
            long diff = finishDate.getTime() - startDate.getTime();
            System.out.printf("MyExecutor: Duration: %d\n", diff);
            System.out.printf("*********************************\n");
        } catch (InterruptedException | ExecutionException e) {
            e.printStackTrace();
        }
    }
}

//8.创建一个SleepTwoSecondsTask类,它实现参数化为String类的Callable接口。实现call()方法。令当前线程睡眠2秒,返回转换为String类型的当前时间。
class SleepTwoSecondsTask implements Callable<String> {
    public String call() throws Exception {
        TimeUnit.SECONDS.sleep(2);
        return new Date().toString();
    }
}

// 9.实现这个例子的主类,通过创建Main类,并实现main()方法。
class Main7 {
    public static void main(String[] args) {
        //10.创建一个MyExecutor对象,名为myExecutor。
        MyExecutor myExecutor = new MyExecutor(2, 4, 1000, TimeUnit.MILLISECONDS, new LinkedBlockingDeque<>());
        //11.创建一个参数化为String类的Future对象的数列,用于存储你将提交给执行者的任务的结果对象。
        List<Future<String>> results = new ArrayList<>();
        //12.提交10个Task对象。
        for (int i = 0; i < 10; i++) {
            SleepTwoSecondsTask task = new SleepTwoSecondsTask();
            Future<String> result = myExecutor.submit(task);
            results.add(result);
        }
        //13.使用get()方法,获取前5个任务的执行结果。将这些信息写入到控制台。
        for (int i = 0; i < 5; i++) {
            try {
                String result = results.get(i).get();
                System.out.printf("Main: Result for Task %d : %s\n", i, result);
            } catch (InterruptedException | ExecutionException e) {
                e.printStackTrace();
            }
        }
        //14.使用shutdown()方法结束这个执行者的执行。
        myExecutor.shutdown();
        //15.使用get()方法,获取后5个任务的执行结果。将这些信息写入到控制台。
        for (int i = 5; i < 10; i++) {
            try {
                String result = results.get(i).get();
                System.out.printf("Main: Result for Task %d :  %s\n", i, result);
            } catch (InterruptedException | ExecutionException e) {
                e.printStackTrace();
            }
        }
        //16.使用awaitTermination()方法等待这个执行者的完成。
        try {
            myExecutor.awaitTermination(1, TimeUnit.DAYS);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        //17.写入一条信息表明这个程序执行的结束。
        System.out.printf("Main: End of the program.\n");
    }
}


2,PriorityBlockingQueue

在执行者框架内部,一个执行者使用一个阻塞队列来存储待处理任务。以任务到达执行者的顺序来存储。一个可能的替代就是使用一个优先级列队来存储新的任务。这样,如果一个高优先级的新任务到达执行者,它将比其他已经在等待执行且低优先级的任务先执行。

//1.创建一个MyPriorityTask类,它实现Runnable接口和参数化为MyPriorityTask类的Comparable接口。
class MyPriorityTask implements Runnable, Comparable<MyPriorityTask> {
    //2.声明一个私有的、int类型的属性priority。
    private int priority;
    private String name;

    public MyPriorityTask(String name, int priority) {
        this.name = name;
        this.priority = priority;
    }

    //5.实现一个方法来返回priority属性的值。
    public int getPriority() {
        return priority;
    }

    // 6.实现声明在Comparable接口中的compareTo()方法。它接收一个MyPriorityTask对象作为参数,
    // 比较这两个对象(当前对象和参数对象)的优先级。让优先级高的任务先于优先级低的任务执行。
    @Override
    public int compareTo(MyPriorityTask o) {
        if (this.getPriority() < o.getPriority()) {
            return 1;
        }
        if (this.getPriority() > o.getPriority()) {
            return -1;
        }
        return 0;
    }

    // 7.实现run()方法。令当前线程睡眠2秒。
    @Override
    public void run() {
        System.out.printf("MyPriorityTask: %s Priority :  %d\n", name, priority);
        try {
            TimeUnit.SECONDS.sleep(2);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}

//8.实现这个例子的主类,通过创建Main类,并实现main()方法。
class Main8 {
    public static void main(String[] args) {
        //9.创建一个ThreadPoolExecutor对象,名为executor。使用参数化为Runnable接口的PriorityBlockingQueue作为执行者用来存储待处理任务的队列。
        ThreadPoolExecutor executor = new ThreadPoolExecutor(1, 1, 1, TimeUnit.SECONDS, new PriorityBlockingQueue<>());
        //10.提交4个使用循环计数器作为优先级的任务给执行者。使用execute()方法提交这些任务给执行者。
        for (int i = 0; i < 4; i++) {
            MyPriorityTask task = new MyPriorityTask("Task " + i, i);
            executor.execute(task);
        }
        //11.令当前线程睡眠1秒。
        try {
            TimeUnit.SECONDS.sleep(1);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        //12.提交4个额外的,使用循环计数器作为优先级的任务给执行者。使用execute()方法提交这些任务给执行者。
        for (int i = 4; i < 8; i++) {
            MyPriorityTask task = new MyPriorityTask("Task " + i, i);
            executor.execute(task);
        }
        //13.使用shutdown()方法关闭这个执行者。
        executor.shutdown();
        //14.使用awaitTermination()方法等待这个执行者的结束。
        try {
            executor.awaitTermination(1, TimeUnit.DAYS);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        //15.写入一条信息表明这个程序的结束。
        System.out.printf("Main: End of the program.\n");
    }
}

3, 实现ThreadFactory接口生成自定义的线程


使用这个工厂,我们集中对象的创建,获取容易改变创建对象的类的优势,或我们创建这些对象的方式,容易限制创建对象的有限资源。比如,我们只能有一个类型的N个对象,就很容易产生关于对象创建的统计数据。
class MyThreadFactory implements ThreadFactory {
    //11.声明一个私有的、int类型的属性counter。
    private int counter;
    //12.声明一个私有的、String类型的属性prefix。
    private String prefix;
    //13.实现这个类的构造器,初始化它的属性。
    public MyThreadFactory(String prefix) {
        this.prefix = prefix;
        counter = 1;
    }

    // 实现newThread()方法。创建一个MyThread对象并增加counter属性值。
    @Override
    public Thread newThread(Runnable r) {
        MyThread myThread = new MyThread(r, prefix + "-" + counter);
        counter++;
        return myThread;
    }
}

4,在一个Executor对象中使用我们的ThreadFactory

在执行者框架(Executor framework)的内部,它提供一个ThreadFactory接口来创建线程,这是用来产生新的线程。
MyThreadFactory threadFactory=new MyThreadFactory("MyThreadFactory");
ExecutorService executor=Executors.newCachedThreadPool(threadFactory);

你已使用Executors类的newCachedThreadPool()方法创建一个Executor对象。你已传入之前创建的工厂对象作为参数,所以已创建的Executor对象将使用这个工厂来创建它所需的线程,并且它将执行MyThread类的线程。

5,定制在计划线程池内运行的任务

计划线程池是 Executor 框架的基本线程池的扩展,允许你定制一个计划来执行一段时间后需要被执行的任务。 它通过 ScheduledThreadPoolExecutor 类来实现,允许运行以下这两种任务:
  • 延时任务:这种任务在一段时间后仅执行一次。
  • 周期性任务:这种任务在延迟后执行,然后通常周期性运行
延时任务可以执行 Callable 和 Runnable 对象,但是周期任务只能执行 Runnable 对象。全部任务通过计划池执行的都必须实现 RunnableScheduledFuture 接口。
//1.  创建一个类,名为 MyScheduledTask,使名为 V 的泛型类型参数化。它扩展 FutureTask 类并实现 RunnableScheduledFuture 接口。
class MyScheduledTask<V> extends FutureTask<V> implements RunnableScheduledFuture<V> {
    //2.   声明一个私有 RunnableScheduledFuture 属性,名为 task.
    private RunnableScheduledFuture<V> task;
    //3.   声明一个私有 ScheduledThreadPoolExecutor,名为 executor.
    private ScheduledThreadPoolExecutor executor;
    //4.   声明一个私有long属性,名为 period。
    private long period;
    //5.   声明一个私有long属性,名为 startDate。
    private long startDate;
    //6.   实现类的构造函数。它接收任务:将要运行的 Runnable 对象,任务要返回的 result,
    // 将被用来创建 MyScheduledTask 对象的 RunnableScheduledFuture 任务,和要执行这个任务的 ScheduledThreadPoolExecutor 对象。
    // 调用它的父类的构造函数并储存任务和执行者属性。
    public MyScheduledTask(Runnable runnable, V result, RunnableScheduledFuture<V> task, ScheduledThreadPoolExecutor executor) {
        super(runnable, result);
        this.task = task;
        this.executor = executor;
    }
    //7.	实现 getDelay() 方法。如果是周期性任务且 startDate 形象的值非0,计算并返回 startDate 属性与当前日期的相差值。
    // 否则,返回储存在 task 属性的原先任务的延迟值。不要忘记你要返回结果时,要传递 time unit 作为参数哦。
    @Override
    public long getDelay(TimeUnit unit) {
        if (!isPeriodic()) {
            return task.getDelay(unit);
        } else {
            if (startDate == 0) {
                return task.getDelay(unit);
            } else {
                Date now = new Date();
                long delay = startDate - now.getTime();
                return unit.convert(delay, TimeUnit.MILLISECONDS);
            }
        }
    }
    //8.  实现 compareTo() 方法。调用原先任务的 compareTo() 方法。
    @Override
    public int compareTo(Delayed o) {
        return task.compareTo(o);
    }
    //9.  实现 isPeriodic() 方法。调用原来任务的 isPeriodic() 方法。
    @Override
    public boolean isPeriodic() {
        return task.isPeriodic();
    }
    //10. 实现方法 run()。如果这是一个周期性任务,你要用下一个执行任务的开始日期更新它的 startDate 属性。
    // 用当前日期和时间间隔的和计算它。 然后,把再次把任务添加到 ScheduledThreadPoolExecutor 对象的 queue中。
    @Override
    public void run() {
        if (isPeriodic() && (!executor.isShutdown())) {
            Date now = new Date();
            startDate = now.getTime() + period;
            executor.getQueue().add(this);
        }
        //11.打印当前日期的信息到操控台,调用 runAndReset() 方法运行任务,然后再打印另一条关于当前日期的信息到操控台。
        System.out.printf("Pre-MyScheduledTask: %s\n", new Date());
        System.out.printf("MyScheduledTask: Is Periodic:%s\n", isPeriodic());
        super.runAndReset();
        System.out.printf("Post-MyScheduledTask: %s\n", new Date());
    }
    //12. 实现 setPeriod() 方法,来确立任务的周期时间。
    public void setPeriod(long period) {
        this.period = period;
    }
}

//13. 创建一个类,名为 MyScheduledThreadPoolExecutor 来实现一个运行 MyScheduledTask 任务的 ScheduledThreadPoolExecutor 对象。特别扩展 ScheduledThreadPoolExecutor 类。
class MyScheduledThreadPoolExecutor extends ScheduledThreadPoolExecutor {
    //14. 实现类的构造函数,只要调用它的父类的构造函数。
    public MyScheduledThreadPoolExecutor(int corePoolSize) {
        super(corePoolSize);
    }
    //15. 实现方法 decorateTask()。它接收将要被运行的 Runnable 对象和将运行 Runnable 对象的 RunnableScheduledFuture 任务作为参数。使用这些对象来构造来创建并返回 MyScheduledTask 任务。
    @Override
    protected <V> RunnableScheduledFuture<V> decorateTask(Runnable runnable, RunnableScheduledFuture<V> task) {
        MyScheduledTask<V> myTask = new MyScheduledTask<V>(runnable, null, task, this);
        return myTask;
    }
    //16. 覆盖方法 scheduledAtFixedRate()。调用它的父类的方法,把返回结果转型成MyScheduledTask对象,
    //调用这个对象的setPeriod()方法设置它的周期。
    @Override
    public ScheduledFuture<?> scheduleAtFixedRate(Runnable command, long initialDelay, long period, TimeUnit unit) {
        ScheduledFuture<?> task = super.scheduleAtFixedRate(command, initialDelay, period, unit);
        MyScheduledTask<?> myTask = (MyScheduledTask<?>) task;
        myTask.setPeriod(TimeUnit.MILLISECONDS.convert(period, unit));
        return task;
    }
}

//17.  创建一个类,名为 Task,实现 Runnable 接口。
class Task implements Runnable {
    //18. 实现方法 run() 。在任务开始时打印一条信息,再让当前线程进入休眠2秒。最后在任务结束时,再打印另一条信息。
    @Override
    public void run() {
        System.out.printf("Task: Begin.\n");
        try {
            TimeUnit.SECONDS.sleep(2);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.printf("Task: End.\n");
    }
}

//19. 创建例子的主类通过创建一个类,名为 Main 并添加 main()方法。
class Main9 {
    public static void main(String[] args) throws Exception {
        //20. 创建一个 MyScheduledThreadPoolExecutor 对象,名为 executor。使用2作为参数来在池中获得2个线程。
        MyScheduledThreadPoolExecutor executor = new MyScheduledThreadPoolExecutor(2);
        //21. 创建 Task 对象,名为 task。把当前日期写入操控台。
        Task task = new Task();
        System.out.printf("Main: %s\n", new Date());
        //22. 使用 schedule() 方法发送一个延迟任务给执行者。此任务在延迟一秒后运行。
        executor.schedule(task, 1, TimeUnit.SECONDS);
        //23. 让主线程休眠3秒。
        TimeUnit.SECONDS.sleep(3);
        //24. 创建另一个 Task 对象。再次在操控台打印当前日期。
        task = new Task();
        System.out.printf("Main: %s\n", new Date());
        //25. 使用方法 scheduleAtFixedRate()发送一个周期性任务给执行者。此任务在延迟一秒后被运行,然后每3秒执行。
        executor.scheduleAtFixedRate(task, 1, 3, TimeUnit.SECONDS);
        //26. 让主线程休眠10秒。
        TimeUnit.SECONDS.sleep(10);
        //27. 使用 shutdown() 方法关闭执行者。使用 awaitTermination() 方法等待执行者的完结。
        executor.shutdown();
        executor.awaitTermination(1, TimeUnit.DAYS);
        //28. 写信息到操控台表明任务结束。
        System.out.printf("Main: End of the program.\n");
    }
}
  • decorateTask() 方法: 这个类扩展 ScheduledThreadPoolExecutor 执行者和它的方法提供一个把 ScheduledThreadPoolExecutor 执行者默认的计划任务转换成 MyScheduledTask 任务来实现的机制。所以,当你实现你的版本的计划任务时,你必须实现你的版本的计划的执行者。
  • The getDelay() 方法被计划的执行者调用来确认它是否需要运行任务。此方法对延迟任务和周期任务的响应是不同的。在之前提到的, MyScheduledTask 类的构造函数接收 原先的将要执行 Runnable 对象的 ScheduledRunnableFuture 对象, 并储存它作为类的属性来获取它的方法和它的数据。当我们要运行延迟任务时,getDelay() 方法返回原先任务的延迟,但是在周期任务的例子中,getDelay() 方法返回 startDate 属性值与当前时间的相差值。
  • run() 方法是用来执行任务的。周期性任务的一个特别之处是你必须把下一次任务的执行作为一个新的任务放入到执行者的queue中,如果你要再次运行任务的话。所以,如果你执行周期性任务,你确定 startDate 属性值通过把当前时间和任务的执行周期相加,然后把任务储存在执行者的queue中。startDate 属性储存下一次任务将开始运行的时间。然后,使用 FutureTask 类提供的 runAndReset() 方法来运行任务。 在这个例子的延迟任务由于他们仅仅执行一次,就不用把他们放入执行者的queue中了。
  • scheduleAtFixedRate() 方法: 我们之前提到的,对于周期任务,你要使用任务的周期来确定 startDate 属性值,但是你还没有初始这个周期呢。你必须重写此方法接收周期作为参数,然后传递给 MyScheduledTask 类这样它才能使用。



  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值