Java线程池解析(上)

一.什么是线程池

二.jdk提供的线程池以及其功能

1.Executor简介

2.jdk提供的线程池

三.线程池的内部实现

1.workQueue(任务队列)

2.拒绝策略

3.自定义线程创建ThreadFactory

4.扩展线程池(线程的开始结束,异常处理等~~~)

例子!

正文:

一:

1.简而言之,使用线程池后创建线程变成了从线程池获取线程,关闭线程变成了向池子归还线程。减少创建线程和归还线程的开销。

二:

1.jdk提供了一套Executor框架,ThreadPoolExecutor表示一个线程池。Executors类则扮演线程池工厂角色,通过Executors可以取得一个具有特定功能的线程池。从UML图中亦可知,ThreadPoolExecutor实现了Executor接口,因此通过这个接口,任何Runnable对象都可以被ThreadPoolExecutor线程池调度。

2.Executor框架提供了各种类型的线程池,主要有以下工厂方法。

public static ExecutorService newFixedThreadPool(int nThreads)
public static ExecutorService newSingleThreadExecutor()
public static ExecutorService newCachedThreadPool()
public static ScheduledExecutorService newSingleThreadScheduledExecutor()
public static ScheduledExecutorService newScheduledThreadPool(int corePoolSize)


以上方法返回了具有不同工作特性的线程池,具体说明如下:(可以先看下边的线程实现在返回来看这些线程池的功能)
⑴. newFixedThreadPool,返回一个固定数量的线程池。当一个新任务提交时,如果有空闲线程,则执行。否则新任务暂存在一个任务队列中,待有空闲时,便处理在任务队列中的任务。
⑵. newSingleThreadExecutor,返回一个线程的线程池。当多余一个新任务提交时,会暂存在一个任务队列中,待有空闲时,按先入先出的顺序处理在任务队列中的任务。
⑶. newCachedThreadPool,返回一个可根据实际情况调整线程数量的线程池,线程数量不确定,若有空闲,则会有限复用线程。否则创建新线程处理任务。所有线程在当前任务执行完后,将返回线程池待复用。
⑷ newSingleThreadScheduledExecutor,返回一个ScheduledExecutorService对象,线程池大小为1。ScheduledExecutorService在Executor接口之上扩展了在给定时间执行某任务的功能。如果在某个固定的延时之后执行,或周期性执行某个任务。可以用这个工厂。
⑸newScheduledThreadPool,也返回一个ScheduledExecutorService对象,但该线程可以指定线程数量。

 

固定线程池

示例:

public class ExecutorExample {
    public static class MyTask implements Runnable{

        @Override
        public void run() {
            System.out.println(Thread.currentThread().getId() + " to do sth...");
            try {
                Thread.sleep(1000);//以防线程执行的太快,只开启一个线程
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }

    public static void main(String[] a ){
        MyTask myTask = new MyTask();

        //创建固定大小线程池
        ExecutorService es = Executors.newFixedThreadPool(5);
        for (int i =0;i<10;i++){
            es.submit(myTask);
        }
                /**
         * 他不会暴力的关闭,而会等待所有线程执行完后关闭线程
         * 可以简单的理解为shutdown只是发送一个关闭信号,
         * 但在shutdown之后,线程就不能再接受其他任务了.
         */
                 es.shutdown();//线程池需要手动关闭,要不主线程结束后和当前线程任务结束也不会退出
        System.out.println("主线程结束");
        /*
        结果:
        主线程结束
        11执行了!
        12执行了!
        15执行了!
        13执行了!
        14执行了!
        12执行了!
        15执行了!
        11执行了!
        13执行了!
        14执行了!

        */

        /*
        //创建动态线程池..有兴趣的可以换成这个  看看运行结果
        ExecutorService escache = Executors.newCachedThreadPool();
        for (int i =0;i<12;i++){
            escache.submit(myTask);
        }
        escache.shutdown();*/

    }
}
计划任务

newScheduledThreadPool返回一个ScheduledExecutorService对象,可以根据实际对线程进行调度。

//在给定的时间delay,对任务进行一次调度
public ScheduledFuture<?> schedule(Runnable command,long delay, TimeUnit unit);
//对任务进行周期性调度,任务调度的频率一定的,它是以上一个任务开始执行时间为起点,之后的period时间后调度下一次任务。
//任务开始于初始延时initialDelay,之后的第一个任务开始于initialDelay+period
//如果任务的执行时间大于调度时间,那么任务就会在上一个任务结束后,立即被调用。
//ps:周期性调度任务并不会无限期的执行,当任务抛出异常会结束,所以请做好异常处理
public ScheduledFuture<?> scheduleAtFixedRate(Runnable command,
                                                  long initialDelay,//开始后这么长时间开始执行任务
                                                  long period,
                                                  TimeUnit unit);//时间单位
//对任务进行周期性调度,在上一个任务结束后,再经过delay长的时间进行任务调度。
public ScheduledFuture<?> scheduleWithFixedDelay(Runnable command,
                                                     long initialDelay,
                                                     long delay,
                                                     TimeUnit unit);
//TimeUnit.SECONDS(单位秒)指定时间单位
//                            还有:
//                            MICROSECONDS    微秒   一百万分之一秒(就是毫秒/1000)
//                            MILLISECONDS    毫秒   千分之一秒    
//                            NANOSECONDS   毫微秒  十亿分之一秒(就是微秒/1000)
//                            SECONDS     秒
//                            MINUTES     分钟
//                            HOURS      小时
//                            DAYS      天


官方解释如下:

示例
 

public class ScheduledExecutorExample {

    public static void main(String[] a){

        ScheduledExecutorService ses = Executors.newScheduledThreadPool(10);
        ses.scheduleAtFixedRate(new Runnable(){
            @Override
            public void run() {
                long s = System.currentTimeMillis();
                try {
                    System.out.println(Thread.currentThread().getId() + " start doSth...");

                    /**
                     * 在这种情况下,输出结果1
                     * 值得注意的是要是任务时长大于周期的时候,任务并不会出现堆叠的现象,而且等任务结束后马上执行下次任务



                     */
                    Thread.sleep(new Random().nextInt(10) * 1000);


                    /* 在这种情况下,输出结果2
                    * 每隔3秒进行一次调度.
                    */
                    //Thread.sleep(1000);

                    long e = System.currentTimeMillis();

                    System.out.println(Thread.currentThread().getId() + " finish..." +( (e -s)/1000) +"s");
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        },1,3, TimeUnit.SECONDS);

        /*
        * 输出结果1
        *   10 start doSth...
            10 finish...8s
            10 start doSth...
            10 finish...4s
            12 start doSth...
            12 finish...8s
            10 start doSth...
            10 finish...4s
            13 start doSth...
            13 finish...3s
            12 start doSth...
            12 finish...7s
            14 start doSth...
        * */

        /*
        *   输出结果2
        *
        *   10 start doSth...
            10 finish...1s
            隔3秒
            10 start doSth...
            10 finish...1s
            隔3秒
            12 start doSth...
            12 finish...1s
            隔3秒
            10 start doSth...
            10 finish...1s
            .....
        * */


另个例子自己敲~~
三.核心线程池的内部实现

对于核心的几个线程池,无论是newFixedThreadPool()、newSingleThreadExecutor()还是newCacheThreadPool方法,虽然看起来创建的线程具有完全不同的功能特点,但其内部均使用了ThreadPoolExecutor实现。

public static ExecutorService newFixedThreadPool(int nThreads) {//固定大小线程池
        return new ThreadPoolExecutor(nThreads, nThreads,
                                      0L, TimeUnit.MILLISECONDS,
                                      new LinkedBlockingQueue<Runnable>());
    }
public static ExecutorService newSingleThreadExecutor() {//一个线程的线程池
        return new FinalizableDelegatedExecutorService
            (new ThreadPoolExecutor(1, 1,
                                    0L, TimeUnit.MILLISECONDS,
                                    new LinkedBlockingQueue<Runnable>()));//无界限的任务队列
    }
public static ExecutorService newCachedThreadPool() {//
        return new ThreadPoolExecutor(0, Integer.MAX_VALUE,
                                      60L, TimeUnit.SECONDS,
                                      new SynchronousQueue<Runnable>());//该队列不接收任务,总是去强行让线程池创建线程执行任务
    }

由以上线程池的实现可以看到,它们都只是ThreadPoolExecutor类的封装。我们看下ThreadPoolExecutor最重要的构造函数:

public ThreadPoolExecutor(
            //指定了线程池中的线程数量
            int corePoolSize,
            //指定了线程池中的最大线程数量
            int maximumPoolSize,
            //当前线程池数量超过corePoolSize时,多余的空闲线程的存活时间,即多少时间内会被销毁。
            long keepAliveTime,
            //keepAliveTime的单位
            TimeUnit unit,
            //任务队列,被提交但尚未被执行的任务,放到任务队列里。//可以自己实现,下文有写
            BlockingQueue<Runnable> workQueue,
            //线程工厂,用于创建线程,一般用默认的即可(Executors.defaultThreadFactory())
            ThreadFactory threadFactory,
            //拒绝策略,当任务太多来不及处理,如何拒绝任务。//可以自己实现,下文有写
            RejectedExecutionHandler handler)

workQueue(任务队列)
只提交但未执行的任务队列,它是一个BlockingQueue接口的对象,仅用于存放Runnable对象,根据队列功能分类,在ThreadPoolExecutor的构造函数中可使用以下几种BlockingQueue。
1. 直接提交的队列:该功能由synchronousQueue对象提供,synchronousQueue对象是一个特殊的BlockingQueue。synchronousQueue没有容量,每一个插入操作都要等待一个响应的删除操作,反之每一个删除操作都要等待对应的插入操作。如果使用synchronousQueue,提交的任务不会被真实的保存,而总是将新任务提交给线程执行,如果没有空闲线程,则尝试创建线程,如果线程数量已经达到了最大值,则执行拒绝策略,因此,使用synchronousQueue队列,通常要设置很大的maximumPoolSize值,否则很容易执行拒绝策略。
2. 有界的任务队列:有界任务队列可以使用ArrayBlockingQueue实现。ArrayBlockingQueue构造函数必须带有一个容量参数,表示队列的最大容量。public ArrayBlockingQueue(int capacity)。当使用有界任务队列时,若有新任务需要执行时,如果线程池的实际线程数量小于corePoolSize,则会优先创建线程。若大于corePoolSize,则会将新任务加入等待队列。若等待队列已满,无法加入,则在总线程数不大于maximumPoolSize的前提下,创建新的线程执行任务。若大于maximumPoolSize,则执行拒绝策略。可见有界队列仅当在任务队列装满后,才可能将线程数量提升到corePoolSize以上,换言之,除非系统非常繁忙,否则确保核心线程数维持在corePoolSize。
3. 无界的任务队列:无界队列可以通过LinkedBlockingQueue类实现。与有界队列相比,除非系统资源耗尽,无界队列的任务队列不存在任务入队失败的情况。若有新任务需要执行时,如果线程池的实际线程数量小于corePoolSize,则会优先创建线程执行。但当系统的线程数量达到corePoolSize后就不再创建了,这里和有界任务队列是有明显区别的。若后续还有新任务加入,而又没有空闲线程资源,则任务直接进入队列等待。若任务创建和处理的速度差异很大,无界队列会保持快速增长,知道耗尽系统内存。
4. 优先任务队列:带有优先级别的队列,它通过PriorityBlokingQueue实现,可以控制任务执行的优先顺序。它是一个特殊的无界队列。无论是ArrayBlockingQueue还是LinkedBlockingQueue实现的队列,都是按照先进先出的算法处理任务,而PriorityBlokingQueue根据任务自身优先级顺序先后执行,在确保系统性能同时,也能很好的质量保证(总是确保高优先级的任务优先执行)。

ThreadPoolExecutor的任务调度逻辑

newFixedThreadPool()方法的实现,它返回一个corePoolSize和maximumPoolSize一样的,并使用了LinkedBlockingQueue任务队列(无界队列)的线程池。当任务提交非常频繁时,该队列可能迅速膨胀,从而系统资源耗尽。
newSingleThreadExecutor()返回单线程线程池,是newFixedThreadPool()方法的退化,只是简单的将线程池数量设置为1.
newCachedThreadPool()方法返回corePoolSize为0而maximumPoolSize无穷大的线程池,这意味着没有任务的时候线程池内没有现场,而当任务提交时,该线程池使用空闲线程执行任务,若无空闲则将任务加入SynchronousQueue队列,而SynchronousQueue队列是直接提交队列,它总是破事线程池增加新的线程来执行任务。当任务执行完后由于corePoolSize为0,因此空闲线程在指定时间内(60s)被回收。对于newCachedThreadPool(),如果有大量任务提交,而任务又不那么快执行时,那么系统变回开启等量的线程处理,这样做法可能会很快耗尽系统的资源,因为它会增加无穷大数量的线程。
使用自定义线程池时,要根据具体应用的情况,选择合适的并发队列作为任务的缓冲。当线程资源紧张时,不同的并发队列对系统行为和性能的影响均不同。

拒绝策略

线程池中的线程已经用完了,无法继续为新任务服务,同时,等待队列也已经排满了,再也塞不下新任务了。这时候我们就需要拒绝策略机制合理的处理这个问题。
JDK内置的拒绝策略如下:

ps:jdk提供的拒绝策略都是ThreadPoolExecutor(线程池)内部类方式来实现的,所以在用的时候得   

new ThreadPoolExecutor.DiscardPolicy()  这样来用

1. AbortPolicy : 直接抛出异常,阻止系统正常运行。

源代码:

  public static class AbortPolicy implements RejectedExecutionHandler {
        public AbortPolicy() { }
        public void rejectedExecution(Runnable r, ThreadPoolExecutor e) {
            throw new RejectedExecutionException("Task " + r.toString() +
                                                 " rejected from " +
                                                 e.toString());
        }
    }

2. CallerRunsPolicy : 只要线程池未关闭,该策略直接在调用者线程中,运行当前被丢弃的任务。显然这样做不会真的丢弃任务,但是,任务提交线程的性能极有可能会急剧下降。

源代码:

 public static class CallerRunsPolicy implements RejectedExecutionHandler {
        public CallerRunsPolicy() { }
        public void rejectedExecution(Runnable r, ThreadPoolExecutor e) {
            if (!e.isShutdown()) {
                r.run();
            }
        }
    }

3. DiscardOldestPolicy : 丢弃最老的一个请求,也就是即将被执行的一个任务,并尝试再次提交当前任务。

 public static class DiscardOldestPolicy implements RejectedExecutionHandler {
        public DiscardOldestPolicy() { }
        public void rejectedExecution(Runnable r, ThreadPoolExecutor e) {
            if (!e.isShutdown()) {
                e.getQueue().poll();
                e.execute(r);
            }
        }
    }

4. DiscardPolicy : 该策略默默地丢弃无法处理的任务,不予任何处理。如果允许任务丢失,这是最好的一种方案。

  public static class DiscardPolicy implements RejectedExecutionHandler {
        public DiscardPolicy() { }
        public void rejectedExecution(Runnable r, ThreadPoolExecutor e) {
        }
    }

以上内置拒绝策略均实现了RejectedExecutionHandler接口,若以上策略仍无法满足实际需要,完全可以自己扩展RejectedExecutionHandler接口。RejectedExecutionHandler的定义如下。

public interface RejectedExecutionHandler {
    /**
     * @param r 请求执行的任务
     * @param executor 当前线程池
     */
    void rejectedExecution(Runnable r, ThreadPoolExecutor executor);
}

自定义线程创建ThreadFactory

自定义线程池
线程池的作用就是为了线程复用,也就是避免线程频繁的创建
但是,最开始的线程从何而来,就是ThreadFactory.

ThreadFactory是一个接口,它有一个方法是创建线程
Thread newThread(Runnable r);

自定义线程可以跟踪线程何时创建,自定义线程名称/组/优先级信息.
甚至可以设置为守护线程.总之自定义线程池可以让我们更加自由的设置线程池中的所有线程状态.

扩展线程池

我们想监控每个人物的执行开始时间 结束时间等细节,我们可以通过扩展ThreadPoolExecutor扩展线程池.他提供了beforExecute(),afterExecute(),和terminated()三个接口对线程池进行控制.

在执行任务的线程中将调用beforeExecute和afterExecute等方法,在这些方法中还可以添加日志、计时、监视或者统计信息收集的功能。无论任务是从run中正常返回,还是抛出一个异常而返回,afterExecute都会被调用。如果任务在完成后带有一个Error,那么就不会调用afterExecute。如果beforeExecute抛出一个RuntimeException,那么任务将不被执行,并且afterExecute也不会被调用。

在线程池完成关闭时调用terminated,也就是在所有任务都已经完成并且所有工作者线程也已经关闭后,terminated可以用来释放Executor在其生命周期里分配的各种资源,此外还可以执行发送通知、记录日志或者手机finalize统计等操作。

 

例子:

/**
 * 重写线程池,捕获了线程运行中的错误,线程开始结束时执行的方法,线程池结束时执行的
 * @author ms
 *
 */
public class TraceThreadPoolExecutor extends ThreadPoolExecutor {

    public TraceThreadPoolExecutor(int corePoolSize, int maximumPoolSize,
            long keepAliveTime, TimeUnit unit,
            BlockingQueue<Runnable> workQueue, ThreadFactory threadFactory,
            RejectedExecutionHandler handler) {
        super(corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue,
                threadFactory, handler);
    }
    
    @Override
    public Future<?> submit(Runnable task) {
        return super.submit(wrap(task,clientTrace(),Thread.currentThread().getName()));
    }
    @Override
    public void execute(Runnable command) {
        super.execute(wrap(command,clientTrace(),Thread.currentThread().getName()));
    }
    @SuppressWarnings("unused")
    private Exception clientTrace(){
        return new Exception("线程池中某个任务报错");
    }
    /**
     * 为传进了的线程增减一层异常处理
     * @param task
     * @param e
     * @param clientThreadName
     * @return
     */
    private Runnable wrap(final Runnable task,Exception e,String clientThreadName){
        return new Runnable() {
            
            @Override
            public void run() {
                try{
                    task.run();
                }catch(Exception e2){
                    e.printStackTrace();
                    e2.printStackTrace();
                    
                }
                
            }
        };
    }
    /**
     * 线程开始前执行,t 将要运行任务的线程,r 将要执行的任务
     */
    @Override
    protected void beforeExecute(Thread t, Runnable r) {
        System.out.println(Thread.currentThread().getName()+"准备运行");
        super.beforeExecute(t, r);
    }
    /**
     * 线程结束后执行,
     */
    @Override
    protected void afterExecute(Runnable r, Throwable t) {
        System.out.println(Thread.currentThread().getName()+"运行结束了!");
        super.afterExecute(r, t);
    }
    /**
     * 线程池退出时执行
     */
    @Override
    protected void terminated() {
        System.out.println("线程池关闭了!");
        super.terminated();
    }
}



public class TraceThreadPoolExecutorDemo implements Runnable{
    int a,b;
    public TraceThreadPoolExecutorDemo(int a,int b){
        this.a=a;
        this.b=b;
    }
    public void run() {
        double i=a/b;
        System.out.println("i---"+i);
    }
    public static void main(String[] args) {
        // 结果1
        ThreadPoolExecutor tp=new ThreadPoolExecutor(5, 5, 0L, TimeUnit.SECONDS, new SynchronousQueue<Runnable>(),
        // 结果2
        //ExecutorService tp=new TraceThreadPoolExecutor(5, 5, 0L, TimeUnit.SECONDS,new ArrayBlockingQueue<Runnable>(10),
        //        Executors.defaultThreadFactory(), //jdk 提供的默认线程工厂
                new ThreadFactory() {//自己定义的线程工厂,可以设置线程类型 名字等
                    
                    @Override
                    public Thread newThread(Runnable r) {
                        Thread t=new Thread(r);
                        //t.setName("自己设定");
                        //t.setDaemon(true);全部设成守护线程
                        return t;
                    }
                },
        //        new ThreadPoolExecutor.DiscardPolicy()  //jdk  提供的几种拒绝策略
                new RejectedExecutionHandler(){ //自己定义拒绝策略
                    //r 为要丢弃的任务,executor为当前线程池
                    //当执行下面方法的时候其实已经丢掉r任务了
                    @Override
                    public void rejectedExecution(Runnable r,
                            ThreadPoolExecutor executor) {
                        //executor.getQueue()  //获取当前线程的任务队列,
                        //r.run();  直接在调用者线程运行被抛弃的任务
                        //if(!executor.isShutdown())上述操作必须要在线程池未关闭的状态下
                        System.out.println(r+"任务被丢弃!");    
                    }
            
                }
        );
        for(int i=0;i<5;i++){
            tp.submit(new TraceThreadPoolExecutorDemo(5,i));
        }
        tp.shutdown();
    }
}

结果1:

i---2.0
i---1.0
i---5.0
i---1.0
结果2:

Thread-0准备运行
Thread-2准备运行
Thread-1准备运行
Thread-4准备运行
Thread-3准备运行
java.lang.Exception: 线程池中某个任务报错
    at com.TraceThreadPoolExecutor.clientTrace(TraceThreadPoolExecutor.java:35)
    at com.TraceThreadPoolExecutor.submit(TraceThreadPoolExecutor.java:27)
    at com.TraceThreadPoolExecutorDemo.main(TraceThreadPoolExecutorDemo.java:54)
java.lang.ArithmeticException: / by zero
    at com.TraceThreadPoolExecutorDemo.run(TraceThreadPoolExecutorDemo.java:19)
    at com.TraceThreadPoolExecutor$1.run(TraceThreadPoolExecutor.java:50)
    at java.util.concurrent.Executors$RunnableAdapter.call(Executors.java:511)
    at java.util.concurrent.FutureTask.run(FutureTask.java:266)
    at com.TraceThreadPoolExecutor$1.run(TraceThreadPoolExecutor.java:50)
    at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1142)
    at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:617)
    at java.lang.Thread.run(Thread.java:745)

Thread-0运行结束了!
i---2.0
i---1.0
i---5.0
i---1.0
Thread-1运行结束了!
Thread-3运行结束了!
Thread-2运行结束了!
Thread-4运行结束了!
线程池关闭了!

说明:线程池本身是不会处理某个任务(线程)的异常的,如果出现异常基本不提示错误(或者你用execute来提交任务会报一点错,但是只是告诉你有错(读者可以自己试试))直接就给你少打印一个结果,如例子   这种简单的错误就算不打印错误 我们也很快就能发现为啥少一个结果,我们把0作为了除数,但是结果1中直接就少了一个线程结果,这不是我们想看到的。

  • 3
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值