美文网首页
线程池的使用

线程池的使用

作者: topshi | 来源:发表于2019-04-17 17:35 被阅读0次

    前言

    多线程软件设计可以最大限度地发挥现代多核处理器地计算能力,提高生产系统地吞吐量和性能。但是,若不加控制而是随意使用线程,系统的性能就会大打折扣。
    那么,随意使用线程会存在什么问题呢?
    真实生产环境下,可能会开启很多线程以支撑应用,而线程数量过大时,反而会耗尽CPU内存资源

    • 虽然与进程相比,线程比较轻量级,但创建和关闭线程需要花费时间。如果每个小任务都创建一个线程,可能创建和销毁线程的时间比任务工作的时间还长,得不偿失。
    • 线程本身需要占用内存资源,大量的线程会抢占内存资源,处理不当会导致out of memery,大量的线程回收会给GC带来很大的压力,延长GC的停顿时间
      因此,我们要使用线程池,控制线程的创建和销毁。

    什么是线程池

    为了避免系统频繁地创建和销毁线程,我们要对创建的线程进行复用。在线程池中,当你需要使用线程时,从线程池中随便拿一个空闲线程,完成工作后,线程不会被销毁,而是归还到线程池中以被其他人使用。通过这种方式,可以节约不少创建和销毁线程对象的时间。

    使用线程池后,我们以往所理解的创建线程就是从线程池中获得空闲线程,处理完线程任务后的关闭线程任务就是往线程池归还线程。

    JDK对线程池的支持

    为了能够更好地控制多线程,JDK提供了一套Executor框架,帮助开发人员控制线程,其本质就是一个线程池。


    Executor框架结构图

    以上成员均来自J.U.C包中,是JDK并发包地核心类。ThreadPoolExecutor表示一个线程池。其次,还提供了一个Executors类,扮演着线程工厂地角色,通过Executors可以获得一个拥有特定功能的线程池。

    Executors提供了以下获得各种类型线程池的工厂方法:

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

    以上工厂方法会返回具有不同工作特性的线程池,

    • newFixedThreadPool()方法:该方法返回一个固定线程数量的线程池,线程的数量不会改变。当一个新的任务提交,若池中有空闲线程,则立即执行,否则新的任务会被暂存至一个任务队列中,待有空闲任务则处理任务队列中的任务。
    • newSingleThreadExecutor()方法:该方法返回一个只有一个线程的线程池。若多于一个线程被提交,任务会被加入到任务队列,待线程空闲按照FIFO的顺序执行。
    • newCachedThreadPool()方法:该方法返回一个可以根据实际情况调整线程数量的线程池。线程池中的线程不是固定的,任务提交时,如果线程池中有空闲线程,则使用空闲线程执行线程任务,否则创建新的线程。
    • newSingleThreadScheduledExecutor()方法:该方法返回一个ScheduledExecutorService对象,线程池大小为1.ScheduledExecutorService接口在ExecutorService接口上扩展了在给定时间执行某任务的功能,如在某个固定的延时之后执行,或者周期性执行某个任务。
    • newScheduledThreadPool(int var0)方法:该方法返回一个ScheduledExecutorService对象,可以指定线程数量。

    固定大小的线程池示例

    /**
     * @Time : 2019/04/17 下午 04:19
     * @Author : xiuc_shi
     **/
    public class ThreadPoolDemo {
        public static class MyTask implements Runnable{
    
            @Override
            public void run() {
                System.out.println(System.currentTimeMillis() + ":Thread ID:" + 
                                   Thread.currentThread().getId());
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }
        public static void main(String[] args) {
            MyTask task = new MyTask();//创建一个线程任务
            ExecutorService es = Executors.newFixedThreadPool(5);//创建一个线程池
            for(int i = 0;i < 10;i++){
                es.submit(task);//提交线程
            }
            es.shutdown();
        }
    }
    >结果
    1555489886561:Thread ID:9
    1555489886561:Thread ID:10
    1555489886562:Thread ID:11
    1555489886562:Thread ID:12
    1555489886562:Thread ID:13
    --------------------------前五个任务和后五个任务相差1000ms
    1555489887561:Thread ID:10
    1555489887561:Thread ID:9
    1555489887562:Thread ID:12
    1555489887562:Thread ID:11
    1555489887562:Thread ID:13
    

    计划任务
    newScheduledThreadPool()方法返回一个ScheduledExecutorService对象,可以根据时间需要对线程进行调度。

    主要方法

    ScheduledFuture<?> schedule(Runnable var1, long var2, TimeUnit var4);
    ScheduledFuture<?> scheduleAtFixedRate(Runnable var1, long var2, 
                                                      long var4, TimeUnit var6);
    ScheduledFuture<?> scheduleWithFixedDelay(Runnable var1, long var2, 
                                                      long var4, TimeUnit var6);
    

    ScheduledExecutorService不像其他线程池,它不一定会立即安排执行任务,而是起到计划执行的作用。它会在指定时间,对任务进行调度。

    • schedule()会在给定时间对任务进行一次调度。
    • scheduleAtFixedRate()scheduleWithFixedDelay()会对任务进行周期性的调度。
      1. FixedRate:从上一个任务的开始执行时间开始计时,经过var4时间调度下一个任务。
      2. FixedDelay:从上一个任务结束开始计时,经过var4时间,调度下一个任务。

    scheduleAtFixedRate()调度示例
    这个任务的执行时间为1秒钟,调度周期为2秒。

    /**
     * @Time : 2019/04/17 下午 05:07
     * @Author : xiuc_shi
     **/
    public class ScheduleAtFixedRateDemo {
        public static class MyTask implements Runnable{
            @Override
            public void run() {
                System.out.println(System.currentTimeMillis()/1000 +
                               ":Thread ID:" + Thread.currentThread().getId());
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }
    
        public static void main(String[] args) {
            ScheduledExecutorService ses = Executors.newScheduledThreadPool(10);
            ses.scheduleAtFixedRate(new MyTask(),0,2, TimeUnit.SECONDS);
        }
    }
    >结果,每隔2秒进行一次调度
    1555492192:Thread ID:9
    1555492194:Thread ID:9
    1555492196:Thread ID:11
    1555492198:Thread ID:9
    1555492200:Thread ID:12
    1555492202:Thread ID:11
    

    如果任务的执行时间大于调度时间会怎样呢?我们设置Thread.sleep(8000);

    >结果,结果是上一个任务执行完后马上调度下一个任务
    1555493505:Thread ID:9
    1555493513:Thread ID:9
    1555493521:Thread ID:11
    1555493529:Thread ID:9
    

    注:调度程序不保证任务会无限期地持续调用,如果任务本身抛出异常,则后续的任务都会被中断,因此要妥善做好异常处理。

    提交线程

    public Future<?> submit(Runnable var1) {
        return this.e.submit(var1);
    }
    es.submit(线程任务);
    
    public void execute(Runnable var1) {
        this.e.execute(var1);
    }
    es.execute(线程任务);
    

    execute()没有返回值,如果不需要线程结果可使用execute方法,性能好;
    submit()返回一个Future对象,如果需要得到线程结果则使用submit方法提交,且能在主线程中通过Future对象的get方法获得线程中的异常。
    关闭线程池

    public void shutdown() {
         this.e.shutdown();
    }
    es.shutdown();
    

    不再接受新的任务,等待之前提交的任务执行完再关闭线程池。

    public List<Runnable> shutdownNow() {
        return this.e.shutdownNow();
    }
    es.shutdownNow();
    

    不再接受新的任务,试图停止池中的任务再关闭线程池,返回所有未处理的线程list集合。

    相关文章

      网友评论

          本文标题:线程池的使用

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