java线程池介绍及简单使用举例

多线程虽然能够提升程序的性能,但其实也是一把双刃剑。"为每一个任务分配一个线程"的问题在于资源管理的复杂性。当我们需要频繁的创建多个线程进行耗时操作时,每次通过new Thread来创建并不是一种好的办法。new Thread 新建和销毁对象的性能较差,线程缺乏统一的管理,而且可能出现无限制的创建线程。

每当看到这种形式的代码时:new Thread(runnable).start()

      并且你希望获得一种更灵活的执行策略,请考虑使用Executor来替代Thread。(参考《Java并发编程实战》)

      因此,我们可以通过使用线程池来管理线程。线程池是指管理一组同构工作线程的资源池。其原理简单解释就是会创建多个线程并进行管理,提交给线程的任务会被线程池指派给其中的线程进行执行。

      java.util.concurrent提供了一种灵活的线程池实现作为Executor框架的一部分,Executor接口如下:

    public interface Executor {
         void execute(Runnable command);
    }
     虽然 Executor是个简单的接口,但它缺位灵活且强大的异步任务执行框架提供了基础。

     线程池都实现了ExecutorService接口,该接口定义了线程池需要实现的方法:

public interface ExecutorService extends Executor {
    void shutdown();
    List shutdownNow();
    boolean isShutdown();
    boolean isTerminated();
    boolean awaitTermination(long timeout, Ti
      meUnit unit)
        throws InterruptedException;
     Future submit(Callable task);
    //.......(代码省略其他用于任务提交的便利方法)
}
    ExecutorService接口的实现有,ThreadPoolExecutor和ScheduledThreadPoolExcecutor。

   ThreadPoolExecutor继承自抽象类AbstractExecutorService,该抽象类实现了ExecutorService接口。ThreadPoolExecutor也是我们运用最多的线程池。

    ScheduledThreadPoolExcecutor 继承了ThreadPoolExecutor 并实现了ScheduledExecutorService接口,ScheduledExecutorService接口继承自ExecutorService接口。ScheduledThreadPoolExcecutor用于周期性的执行任务,和Timer类类似。但Timer存在一些缺陷,因此应该考虑使用ScheduledThreadPoolExcecutor来代替它(Timer支持基于绝对时间而不是相对时间的调度机制,因此任务的执行对系统时钟变化很敏感,而ScheduledThreadPoolExcecutor只支持基于相对时间的调度)。

    我们可以通过ThreadPoolExecutor的构造函数来实例化一个对象,但由于创建参数相对复杂,通常选择Exectors工厂类的静态方法来创建一个线程池:

    newFixedThreadPool。将创建一个固定长度的线程池,每当提交一个任务时就创建一个线程池,直到达到线程池的最大数量,这时线程池的规模将不再变化。

    newCachedThreadPool。将创建一个可缓存的线程池,如果线程池的当前规模超过了处理需求时,那么将回收空闲的线程,当需求增加时,则可以添加新的线程,线程池的规模不存在任何限制。

    newSingleThreadPool。是一个单线程的Executor,它创建单个工作线程来执行任务,如果这个线程异常结束,会创建另一个线程来替代。newSingleThreadPool能确保依照任务在队列中的顺序来串行执行(例如FIFO、LIFO、优先级)。

    newScheduledThreadPool。创建了一个固定长度的线程池,而且以延迟或定时的方式来执行任务,类似于Timer。

    newFixedThreadPool和newCachedThreadPool 返回通用的ThreadPoolExecutor实例,这些实例可以直接用来构造专门用途的executor。


   当默认的创建线程池策略无法满足要求时,那么可以通过ThreadPoolExecutor构造函数来实例化一个对象,根据自己的需求来实现定制,ThreadPoolExecutor最常见的构造函数形式如下:

    

public ThreadPoolExecutor(int corePoolSize,
                              int maximumPoolSize,
                              long keepAliveTime,
                              TimeUnit unit,
                              BlockingQueue workQueue,
                              ThreadFactory threadFactory,
                              RejectedExecutionHandler handler) 
参数说明:

corePoolSize:线程池中所保存的核心线程数。线程池启动后默认是空的,只有任务来临时才会创建线程以处理请求。

maximumPoolSize:线程池允许创建的最大线程数。它与corePoolSize的作用是调整“线程池中实际运行的线程的数量”。当新任务提交给线程池时,如果线程池中运行的线程数量小于corePoolSize,则创建新线程来执行任务;如果此时,线程池中运行的线程数量大于corePoolSize,但小于maximumPoolSize,则仅当阻塞队列满时才创建新线程。如果corePoolSize与maximumPoolSize相同,则创建固定大小的线程池。如果将maximumPoolSize设置为基本的无界值(如Integer.MAX_VALUE),则允许线程池适应任意数量的并发任务。

keepAliveTime:当前线程池线程总数达到核心线程数时,终止多余的空闲线程的时间。

Unit:keepAliveTime参数的时间单位,可选值有毫秒、秒、分等。

workQueue:任务队列。如果当前线程池达到核心线程数量corePoolSize后,且当前所有线程都处于活动状态时,则将新加入的任务放到此队列中。

threadFactory:线程工厂,让用户可以定制线程的创建过程,一般不需要设置。

Handler:拒绝策略,当线程池和任务队列workQueue都满了的情况下,对新加的任务采取的处理策略。

下面通过两个简单的例子来说明线程池的简单使用:

1.通过Executors.newFixedThreadPool(int)来创建一个固定数量的线程池,代码如下:

public class MyExecutorDemo {
    //执行的任务数量
    private static int MAX = 10;

    public static void main(String args[]){
        try {
            fixedThreadPool(4);
        } catch (InterruptedException e) {
            e.printStackTrace();
        } catch (ExecutionException e) {
            e.printStackTrace();
        }
    }
    private static void fixedThreadPool (int coreSize)
            throws InterruptedException,ExecutionException {
        //创建线程池
        ExecutorService exec = Executors.newFixedThreadPool(coreSize);
        for(int i = 0; i < MAX; i++){
            //提交任务
            Future task = exec.submit(new Callable() {
                @Override
                public Integer call() throws Exception {
                    System.out.println("执行线程" + Thread.currentThread().getName());
                    return fibc(20);
                }
            });
            //获取执行结果
            System.out.println("第"+i+"次计算,结果为"+task.get());
        }
    }
    //模拟耗时操作,定义一个斐波那契数列
    private static int fibc(int num){
        if (num == 0){
            return 0;
        }
        if (num == 1){
            return  1;
        }
        return fibc(num-1)+fibc(num-2);
    }

}
在上述代码中,通过fixedThreadPool启动了含有4个线程的线程池,我们看一下该方法的构造函数,

public static ExecutorService newFixedThreadPool(int nThreads) {
        return new ThreadPoolExecutor(nThreads, nThreads,
                                      0L, TimeUnit.MILLISECONDS,
                                      new LinkedBlockingQueue());
    }
第一个和第二个参数时相同的值,则创建固定大小的线程池,最后的参数为无界队列,因此该线程池可容纳无限个任务。创建成功后,向线程池中通过submit提交了10个Callable任务,每个任务计算前20个的斐波那契数列。通过结果可以看出,线程池中有4个线程交替的执行任务,执行运算结果如下:

执行线程pool-1-thread-1
第0次计算,结果为6765
执行线程pool-1-thread-2
第1次计算,结果为6765
执行线程pool-1-thread-3
第2次计算,结果为6765
执行线程pool-1-thread-4
第3次计算,结果为6765
执行线程pool-1-thread-1
第4次计算,结果为6765
执行线程pool-1-thread-2
第5次计算,结果为6765
执行线程pool-1-thread-3
第6次计算,结果为6765
执行线程pool-1-thread-4
第7次计算,结果为6765
执行线程pool-1-thread-1
第8次计算,结果为6765
执行线程pool-1-thread-2
第9次计算,结果为6765

newCachedThreadPool

2.通过Executors.newCacheThreadPool()创建带缓存的线程池

    有时,我们需要任务尽可能快的被执行,这需要线程池中的线程足够多,也就是说需要用空间来换时间,创建的线程越多,占用的内存消耗也越大。但由于其并发量也越大,因此执行的速度也越快。考虑这样一种场景,在新提交了一个任务后,由于当前没有空闲线程可执行,因此需要马上创建一个线程来执行该任务。这种场景可通过该方法来实现。代码如下:

private static void newCachedThreadPool () throws ExecutionException,
            InterruptedException{

        ExecutorService executorService = Executors.newCachedThreadPool();
        for (int i = 0; i < MAX; i++){
            executorService.submit(new Runnable() {
                @Override
                public void run() {
                    System.out.println("执行线程为:"+Thread.currentThread().getName()+
                            ",结果为:"+fibc(30));
                }
            });
        }
    }

代码中,通过 newFixedThreadPool创建了一个线程池,并每次提交一个任务,来计算前30个的斐波那契数列,运行结果如下:

执行线程为:pool-1-thread-2,结果为:832040
执行线程为:pool-1-thread-1,结果为:832040
执行线程为:pool-1-thread-3,结果为:832040
执行线程为:pool-1-thread-4,结果为:832040
执行线程为:pool-1-thread-1,结果为:832040
执行线程为:pool-1-thread-5,结果为:832040
执行线程为:pool-1-thread-2,结果为:832040
执行线程为:pool-1-thread-6,结果为:832040
执行线程为:pool-1-thread-1,结果为:832040
执行线程为:pool-1-thread-5,结果为:832040

为了保证执行效率,每次提交任务,线程池都会创建一个新线程来执行任务,但前提是此时没有空闲线程才创建新线程,但有空闲线程时,则会使用空闲的线程来执行任务,如结果中所示,执行前4个任务时,线程池为每个任务都创建了一个线程,当执行到第5个任务时,此时第一个线程已经执行完任务,并处于空闲状态,那么第5个任务就被执行在第1个线程中了。

(参考书籍《Java并发编程实战》、《Android开发进阶从小工到专家》)






     



你可能感兴趣的:(多线程,并发)