美文网首页
线程池详解(二)

线程池详解(二)

作者: 12点前睡觉hhh | 来源:发表于2019-05-31 14:33 被阅读0次

1.ThreadFactory

线程池的主要作用就是复用,也就是避免线程的频繁创建。那么最开始的线程从何而来呢?那就是ThreadFactory。
ThreadFactory是一个接口,它只有一个用来创建线程的方法。

Thread newThread(Runnable r);

当线程池需要新建线程的时候,就会调用这个方法。
使用自定义线程池,我们可以跟踪线程池究竟在何时创建了多少线程,也可以自定义线程的名称,组以及优先级信息,甚至可以将所有线程设置为守护线程。
ThreadFactory简单实例代码如下:

public class ThreadFactoryDemo {
    public static void main(String[] args) {
        ExecutorService es=new ThreadPoolExecutor(5, 10,
                0L, TimeUnit.SECONDS,
                new LinkedBlockingDeque<Runnable>(),
                new ThreadFactory() {
                    @Override
                    public Thread newThread(Runnable r) {
                        Thread t=new Thread(r);
                        System.out.println(System.currentTimeMillis()+"creat"+t);
                        return t;
                    }
                });
        for (int i=0;i<10;i++){
            es.submit(()->{
                try {
                    System.out.println(System.currentTimeMillis()+" "+Thread.currentThread().getId()+"正在执行");
                    Thread.sleep(2000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }finally {
                    System.out.println(Thread.currentThread().getId()+"关闭了");
                }
            });
        }
    }
}

2.扩展线程池

虽然JDK已经帮我们实现了这个稳定的高性能线程池,但是如果我们需要对这个线程池做一些扩展,但如果我们需要对线程池做一些扩展,比如监控每个任务执行的开始时间和结束时间,或者其他一些自定义的增强功能,这时候应该怎么办?
ThreadPoolExecutor是一个可扩展的线程池。它提供了beforeExecute(),afterExecute和terminated()接口三个接口用来对线程池进行控制。 可以重写ThreadPoolExecutor中的这三个方法。
实例如下:

public class ThreadFactoryDemo {
    public static void main(String[] args) {
        ExecutorService es=new ThreadPoolExecutor(5, 10,
                0L, TimeUnit.SECONDS,
                new LinkedBlockingDeque<Runnable>(),
                new ThreadFactory() {
                    @Override
                    public Thread newThread(Runnable r) {
                        Thread t=new Thread(r);
                        System.out.println(System.currentTimeMillis()+"creat"+t);
                        return t;
                    }
                }){
            @Override
            protected void beforeExecute(Thread t, Runnable r) {
                System.out.println("准备执行"+t);
            }

            @Override
            protected void afterExecute(Runnable r, Throwable t) {
                System.out.println("执行完成"+r);
            }

            @Override
            protected void terminated() {
                System.out.println("线程池退出");
            }
        };
        for (int i=0;i<10;i++){
            es.submit(()->{
                try {
                    System.out.println(System.currentTimeMillis()+" "+Thread.currentThread().getId()+"正在执行");
                    Thread.sleep(2000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }finally {
                    System.out.println(Thread.currentThread().getId()+"关闭了");
                }
            });
        }
    }
}

3.关闭线程池

关闭线程池,可以通过shutdown和shutdownNow两个方法,它们的原理都是遍历线程池中所有的线程,然后依次中断线程。但是两者还是有不同。
1.shutdownNow首先将线程池的状态设为stop,然后尝试停止正在执行和未执行任务的线程,并返回等待执行任务的列表。
2.shutdown只是将线程池的状态设置为shutdown状态,然后中断所有没有正在执行任务的线程。
可以看出shutdown方法会将正在执行的任务继续执行完,而shutdownNow方法会直接中断正在执行的任务。调用了这两个方法的任意一个,isShutDown方法都会返回true,当所有线程都关闭,才表示线程池关闭成功,这是调用isTerminated方法才会返回true。
使用实例代码:

public class ThreadFactoryDemo {
    public static void main(String[] args) {
        ExecutorService es=new ThreadPoolExecutor(5, 10,
                0L, TimeUnit.SECONDS,
                new LinkedBlockingDeque<Runnable>(),
                new ThreadFactory() {
                    @Override
                    public Thread newThread(Runnable r) {
                        Thread t=new Thread(r);
                        System.out.println(System.currentTimeMillis()+"creat"+t);
                        return t;
                    }
                }){
            @Override
            protected void beforeExecute(Thread t, Runnable r) {
                System.out.println("准备执行"+t);
            }

            @Override
            protected void afterExecute(Runnable r, Throwable t) {
                System.out.println("执行完成"+r);
            }

            @Override
            protected void terminated() {
                System.out.println("线程池退出");
            }
        };
        for (int i=0;i<10;i++){
            es.submit(()->{
                try {
                    System.out.println(System.currentTimeMillis()+" "+Thread.currentThread().getId()+"正在执行");
                    Thread.sleep(2000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }finally {
                    System.out.println(Thread.currentThread().getId()+"关闭了");
                }
            });
        }
        es.shutdown();
        while (true){
            if (es.isTerminated())
                break;
        }
        System.out.println("线程池关闭成功");
    }
}

4.如何合理配置线程池参数

Ncpu=CPU的数量
Ucpu=目标CPU的使用率
W/C=等待时间与计算时间的比率
Nthreads=Ncpu x UCPU x (1+W/C)
要想合理的配置线程池,就必须首先分析任务特性,可以从以下几个角度进行分析:
1.CPU的性质:CPU密集型任务、IO密集型任务和混合型任务
2.任务的优先级:高、中、低
3.任务的执行时间:长、中和短
4/任务的依赖性:是否依赖其他系统资源比如数据库的连接。

任务性质不同的任务可以用不同规模的线程池分开处理。CPU密集型任务配置尽可能少的线程数,如配置Ncpu+1个小城的线程池。IO密集型任务则由于需要等待IO操作,线程并不是一直在执行任务,则配置尽可能多的线程,如2xNcpu。混合型的任务,如果可以拆分,则将其拆分成一个CPU密集型任务和一个IO密集型任务,只要这两个任务执行的时间相差不是太大,则没有必要分解。我们可以通过Runtime.getRuntime().availableProcessors()方法获得当前设备的CPU个数。
优先级不同的任务可以使用优先级队列PrioityBlockingQueue来处理。
执行时间不同的任务可以交给不同规模的线程池来处理,或者也可以使用优先级队列,让执行时间段的先执行。
依赖数据库连接池的任务,因为线程提交SQL后需要等待数据库返回结果,如果等待的时间越长,CPU空闲时间就越长,那么线程数应该设置越大,这样才能更好的利用CPU。
并且,阻塞队列最好使用有界队列,如果采用无界队列的话,一旦任务积压在阻塞队列中的话就会占用过多的内存资源,甚至会使得系统崩溃。

相关文章

网友评论

      本文标题:线程池详解(二)

      本文链接:https://www.haomeiwen.com/subject/nzswtctx.html