Java线程池基本使用

Posted Andoter

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Java线程池基本使用相关的知识,希望对你有一定的参考价值。

无论在Java的开发中还是在android的开发中,线程都占有重要的地位,所以今天就来说说线程池的东西。

一、线程池概述

在Android开发中,我们经常把一个耗时任务放在一个线程中进行执行,目的就是为了避免ANR异常。但是如果我们在一个页面开了很多线程,线程在短时间内执行结束,我们这样频繁的创建线程就降低了系统的运行效率。所以就有了线程池。线程池的作用是什么呢?
线程池会根据系统的环境变量,自动或手动配置一个线程池中的线程数量,使线程的创建和回收达到一个理想的状态,减少了系统资源的消耗,提高系统的效率。这样我们就得出了使用线程池的几个好处:

  • 降低资源消耗。通过重复利用已创建的线程降低线程创建和销毁造成的消耗
  • 通过线程池对线程进行管理,提高了系统的承受力。(每个线程打越需要1MB内存)。
  • 提高响应速度。当任务到达时,任务可以不需要等到线程创建就能立即执行

二、线程池的基本使用

在jdk1.5中,java给我们提供了java.util.concurrent包,这个包里就是关于我们线程池的使用接口和类。线程池的顶级接口就是Executor接口。它有一个execute(Runnable)方法用来执行所提交的Runnable任务。它只是一个接口,所以需要有它的实现体:

scsd

配置线程池是一件比较复杂的工作,如果我们对线程池的原理不是很了解,很容易导致配置的线程池达不到效果。所以系统给我们提供了Executors类,它提供一些静态方法帮助我们创建一些常用的线程池。

  • public static ExecutorService newFixedThreadPool(int nThreads):创建固定数目线程的线程池。每次提交一个任务就创建一个线程,直到线程达到线程池的最大大小。线程池的大小一旦达到最大值就会保持不变,如果某个线程因为执行异常而结束,那么线程池会补充一个新线程。
  • public static ExecutorService newCachedThreadPool():创建一个可缓存的线程池。如果线程池的大小超过了处理任务所需要的线程,那么就会回收部分空闲(60秒不执行任务)的线程,当任务数增加时,此线程池又可以智能的添加新线程来处理任务。此线程池不会对线程池大小做限制,线程池大小完全依赖于操作系统(或者说JVM)能够创建的最大线程大小。
  • public static ScheduledExecutorService newScheduledThreadPool(int corePoolSize):创建一个支持定时及周期性的任务执行的线程池,多数情况下可用来替代Timer类。
  • public static ExecutorService newSingleThreadExecutor():创建一个单线程的线程池。这个线程池只有一个线程在工作,也就是相当于单线程串行执行所有任务。如果这个唯一的线程因为异常结束,那么会有一个新的线程来替代它。此线程池保证所有任务的执行顺序按照任务的提交顺序执行。

测试案例:

我们创建一个Text的线程,用于测试:

    class Text implements Runnable{
        String name;
        public Text(String name){
            this.name = name;
        }

        @Override
        public void run() {
            System.out.println(Thread.currentThread().getName() + "--" + name);
        }
    }

现在我们开始测试线程池的效果:

1、ExecutorService newFixedThreadPool(int nThreads)创建指定大小的线程池数。
    public static void main(String[]args){
        //我们创建一个指定大小为3的线程池
        ExecutorService exServiceFixed = Executors.newFixedThreadPool(3);
        Text t1 = new Text("t1");
        Text t2 = new Text("t2");
        Text t3 = new Text("t3");
        Text t4 = new Text("t4");
        Text t5 = new Text("t5");
        exServiceFixed.execute(t1);
        exServiceFixed.execute(t2);
        exServiceFixed.execute(t3);
        exServiceFixed.execute(t4);
        exServiceFixed.execute(t5);
        exServiceFixed.shutdown();
    }

结果:

    pool-1-thread-1--t1
    pool-1-thread-1--t4
    pool-1-thread-1--t5
    pool-1-thread-2--t2
    pool-1-thread-3--t3

我们可以看到尽管我们用线程池执行了5个线程任务,但是在线程池内部仅仅有我们指定的3个线程在工作,所以达到了提高效率的作用。

2、ExecutorService newCachedThreadPool()创建一个可重用不指定大小的线程池
    public static void main(String[]args){
        //我们创建一个可重用的线程池
        ExecutorService exServiceCached = Executors.newCachedThreadPool();
        Text t1 = new Text("t1");
        Text t2 = new Text("t2");
        Text t3 = new Text("t3");
        Text t4 = new Text("t4");
        Text t5 = new Text("t5");
        exServiceCached.execute(t1);
        exServiceCached.execute(t2);
        exServiceCached.execute(t3);
        exServiceCached.execute(t4);
        exServiceCached.execute(t5);
        exServiceCached.shutdown();
    }

运行结果:

    pool-1-thread-1--t1
    pool-1-thread-2--t2
    pool-1-thread-3--t3
    pool-1-thread-4--t4
    pool-1-thread-5--t5

线程池中开了5个线程,这个线程池的特点就是可重用,不限制大小,数量以JVM在系统中能创建的大小为准。

3、ScheduledExecutorService newScheduledThreadPool(int corePoolSize)创建定时的线程池。

例如:

    public static void main(String[]args){
        //我们创建一个可重用的线程池
        ScheduledExecutorService scheduledExecutorService = Executors.newScheduledThreadPool(2);
        Text t1 = new Text("t1");
        Text t2 = new Text("t2");
        Text t3 = new Text("t3");
        Text t4 = new Text("t4");
        Text t5 = new Text("t5");
        scheduledExecutorService.schedule(t1, 2000, TimeUnit.MILLISECONDS);
        scheduledExecutorService.schedule(t2, 2000, TimeUnit.MILLISECONDS);
    }

注意,此时使用的ScheduledExecutorService,它是ExecutorService接口的实现接口。schedule方法,用来定时任务,线程会在指定时间后进行执行。

另外,它还有定时持续的任务,

    scheduledExecutorService.scheduleAtFixedRate(t1, 1000,2000, TimeUnit.MILLISECONDS);
    scheduledExecutorService.scheduleAtFixedRate(t2, 1000,2000, TimeUnit.MILLISECONDS);
    scheduledExecutorService.scheduleWithFixedDelay(t1, 1000,2000, TimeUnit.MILLISECONDS);

每隔一段时间就去执行任务。

    pool-1-thread-2--t1
    pool-1-thread-1--t2
    pool-1-thread-2--t1
    pool-1-thread-1--t2
    pool-1-thread-2--t1
    pool-1-thread-1--t2
    pool-1-thread-2--t1
    pool-1-thread-1--t2
    pool-1-thread-2--t1
    pool-1-thread-2--t2
    pool-1-thread-2--t1
    pool-1-thread-1--t2
    pool-1-thread-2--t1
    pool-1-thread-1--t2
    pool-1-thread-2--t1
    pool-1-thread-1--t2
    pool-1-thread-2--t1
    pool-1-thread-1--t2
4、ExecutorService newSingleThreadExecutor创建单线程线程池
    public static void main(String[]args){
        //我们创建一个可重用的线程池
        ExecutorService executorServiceSingle = Executors.newSingleThreadExecutor();
        Text t1 = new Text("t1");
        Text t2 = new Text("t2");
        Text t3 = new Text("t3");
        executorServiceSingle.execute(t1);
        executorServiceSingle.execute(t2);
        executorServiceSingle.execute(t3);
    }

结果:

    pool-1-thread-1--t1
    pool-1-thread-1--t2
    pool-1-thread-1--t3

我们可以看到只有一个线程在执行我们的任务。

三、线程池的补充知识

这里补充一个知识点就是ExecutorService.submit()方法。这个方法有三个重载:

  • Future submit(Callable task);
  • Future submit(Runnable task, T result);
  • Future
    public interface Callable<V> {
        /**
         * Computes a result, or throws an exception if unable to do so.
         *
         * @return computed result
         * @throws Exception if unable to compute a result
         */
        V call() throws Exception;
    }

什么都没实现,就一个call()方法,根据注释,这个方法用来计算某个结果。

Future的源码结构:

    public interface Future<V> {
        boolean cancel(boolean mayInterruptIfRunning);
        boolean isCancelled();
        boolean isDone();
        V get() throws InterruptedException, ExecutionException;
        V get(long timeout, TimeUnit unit)
            throws InterruptedException, ExecutionException, TimeoutException;
    }

  在Future接口中声明了5个方法,下面依次解释每个方法的作用:

  • cancel方法用来取消任务,如果取消任务成功则返回true,如果取消任务失败则返回false。参数mayInterruptIfRunning表示是否允许取消正在执行却没有执行完毕的任务,如果设置true,则表示可以取消正在执行过程中的任务。如果任务已经完成,则无论mayInterruptIfRunning为true还是false,此方法肯定返回false,即如果取消已经完成的任务会返回false;如果任务正在执行,若mayInterruptIfRunning设置为true,则返回true,若mayInterruptIfRunning设置为false,则返回false;如果任务还没有执行,则无论mayInterruptIfRunning为true还是false,肯定返回true。
  • isCancelled方法表示任务是否被取消成功,如果在任务正常完成前被取消成功,则返回 true。
  • isDone方法表示任务是否已经完成,若任务完成,则返回true;
  • get()方法用来获取执行结果,这个方法会产生阻塞,会一直等到任务执行完毕才返回;
  • get(long timeout, TimeUnit unit)用来获取执行结果,如果在指定时间内,还没获取到结果,就直接返回null。
      也就是说Future提供了三种功能:

      1)判断任务是否完成;

      2)能够中断任务;

      3)能够获取任务执行结果。

      因为Future只是一个接口,所以是无法直接用来创建对象使用的,因此就有了下面的FutureTask。

也不复杂,几个方法判断任务是否结束,以及通过get方法获取任务的结果。我们直接看一个例子吧!

    public class MainDemo {
        public static void main(String[]args){
            //我们创建一个可重用的线程池
            ExecutorService executorServiceSingle = Executors.newSingleThreadExecutor();
            Future<Integer> future = executorServiceSingle.submit(new AddCallable());
            try {
                System.out.println(future.get());
            } catch (InterruptedException e) {
                e.printStackTrace();
            } catch (ExecutionException e) {
                e.printStackTrace();
            }
        }
    }

    class AddCallable implements Callable<Integer>{

        @Override
        public Integer call() throws Exception {
            //执行我们的业务逻辑处理
            return 2+3;
        }
    }

我们实现一个Callable接口用来处理我们的任务,然后通过Future来获取任务的结果。果Executor后台线程池还没有完成Callable的计算,这调用返回Future对象的get()方法,会阻塞直到计算完成。这也就是为什么需要捕捉InterruptedException异常的原因。这点是不是跟我们Android的下载任务差不多。开启一个下载任务,然后通过Handler发送的UIThread中进行处理。

同样还有一种组合Callable+FutureTask

    Task task = new Task();
    FutureTask<Integer> futureTask = new FutureTask<Integer>(task);
    Thread thread = new Thread(futureTask);
    thread.start();

四、总结

本着复习知识点的原则,并没有介绍线程池的配置,大家可以去搜索相关资料学习下,复习这个的知识点,就是准备这几天写个Android网络请求的简单框架使用,所以知识点也知识简单介绍了用法,具体的深入分析没做。

参考文章:

http://blog.csdn.net/sd0902/article/details/8395677
http://www.cnblogs.com/dolphin0520/p/3949310.html
http://www.cnblogs.com/dolphin0520/p/3932921.html
http://www.iteye.com/topic/366591

======================================
作者:mr_dsw
地址:http://blog.csdn.net/mr_dsw
理念:转载注明出处,分享是进步的源泉

======================================

以上是关于Java线程池基本使用的主要内容,如果未能解决你的问题,请参考以下文章

Java线程池详解

Java 线程池详解

newCacheThreadPool()newFixedThreadPool()newScheduledThreadPool()newSingleThreadExecutor()自定义线程池(代码片段

Java线程池详解

IDEA对新建java线程池的建议

Java线程池基本使用