关于java:工作三年小胖问我什么是线程池真的菜

27次阅读

共计 8151 个字符,预计需要花费 21 分钟才能阅读完成。

欢送来到狗哥多线程系列连载。本篇是线程相干的第六篇,前五篇别离是:

创立线程到底有几种形式?

线程有多少种状态?Runnable 肯定在执行工作吗?

万字长文,Thread 类源码解析!

wait、notify/notifyAll 解析

线程之生产者消费者模式

什么是线程池?

线程池是一种池化技术,简略来说就是一个治理线程的池子。这个池子外面的线程数是固定的,当工作数量大于线程数量时,会对线程进行复用。一个线程执行完工作,就回到这期待下一个工作的招唤,也不要你销毁。相似的还有咱们工作常接触的数据库连接池。java 中的线程池次要是 juc(java.util.concurrent)包来复制,次要是由 ThreadPoolExecutor 和 ScheduledThreadPoolExecutor 类来实现,后者在前者的根底上减少了定时执行的性能。

为什么应用线程池?

那为什么要应用线程池呢?手动创立不香吗?真的不香,手动创立的情景仅仅适宜很少任务量的状况。比方:只有一个工作,这问题不大。

public class OneTask {public static void main(String[] args) {Thread thread0 = new Thread(new Task());
        thread0.start();}

    static class Task implements Runnable {public void run() {System.out.println("Thread Name:" + Thread.currentThread().getName());
        }
    }
    
}

那如果我就是有 10000 个工作呢?要这样写吗?

public class OneTask {public static void main(String[] args) {for (int i = 0; i < 10000; i++) {Thread thread0 = new Thread(new Task());
            thread0.start();}
    }

    static class Task implements Runnable {public void run() {System.out.println("Thread Name:" + Thread.currentThread().getName());
        }
    }

}

运行后果:

Thread Name: Thread-9977
Thread Name: Thread-9975
Thread Name: Thread-9973
Thread Name: Thread-9951
Thread Name: Thread-9999

Excuse me? 创立 10000 个线程?疯了吧?java 是一门高级语言,很多底层的工作对咱们来说都是黑盒,比方垃圾主动回收。每一个线程从创立到销毁都要占用资源,用完须要回收的

10000 个线程造成的垃圾回收开销得有多大呀,如果还是须要消耗肯定工夫的工作呢?要是我的线程工作很简略就是打印个日志,应用线程的内存开销比工作执行自身的开销还要大,这时就会得失相当。

简而言之,频繁创立线程带来两点很不敌对的问题:

  • 重复创立线程零碎开销比拟大,每个线程创立和销毁都须要工夫。
  • 过多的线程会占用过多的内存等资源,还会带来过多的上下文切换,同时还会导致系统不稳固。

但我的工作的确多,咋办?这个时候线程池就呈现了,它的呈现解决了以上两点问题。

首先,针对重复创立线程开销大的问题,线程池用固定数量的线程放弃工作状态并复用

其次,针对过多线程占用太多内存资源的问题,线程池依据须要创立线程,控制线程的总数量,防止占用过多内存资源

java 的线程池

线程池嘛,就是个池子。这外面的线程是固定的且可控的,java 提供了 Executor 接口不便咱们实现线程池,它的继承关系是这样的:

其中 ThreadPoolExecutor 和 ScheduledThreadPoolExecutor 是实现线程池的两个类,区别上文说过了。

另外,还有个 JDK1.7 才呈现的线程池:ForkJoinPool,它适宜执行可产生子工作的工作,第一步是拆分也就是 Fork,第二步是汇总也就是 Join。继承关系是这样的(前面再独自出一期专门钻研这个线程池)。

线程池的执行流程

1、首先提交工作,查看外围线程池是否已满?满了丢进队列。未满则创立线程执行工作。
2、队列是否已满?满了查看整个线程池是否已满?未满则是增加到队列中排队期待。
3、整个线程池都没可用线程了,间接依据回绝策略解决新工作。

线程池的参数

找到 ThreadPoolExecutor 的构造方法:

public ThreadPoolExecutor(int corePoolSize,
    int maximumPoolSize,
    long keepAliveTime,
    TimeUnit unit,
    BlockingQueue < Runnable > workQueue,
    ThreadFactory threadFactory,
    RejectedExecutionHandler handler) {}

它一共有如下 7 个参数:

ThreadPoolExecutor 结构传入这 7 个参数,就能够创立一个线程池了,上面逐个解释:

1、corePoolSize 是外围线程数,就是指定线程池有多少始终沉闷的线程,这个是依据业务需要定的,线程池执行过程的第一步就是查看外围线程数是不是都曾经满了。

2、maximumPoolSize 是整个线程池的最大线程数,超出外围线程的局部有闲暇,是能够进行回收的。所以失常状况下,线程池中的线程数量会处在 corePoolSize 与 maximumPoolSize 的闭区间内。

二者区别

maximumPoolSize 蕴含 corePoolSize 和 maximumPoolSize 减 corePoolSize。他两就像短工和临时工的区别。打个比方外包公司接到大我的项目,须要 100 个程序员能力搞定,可公司外部就只有 10 个猿。咋办?招 90 个长期的呗,干完活就开掉。原有的 10 个就是短工对应 corePoolSize,即便没这我的项目他在公司还有活干。而剩下就 90 个就是长期的,对应 maximumPoolSize – corePoolSize,做完我的项目就不须要了。残暴吧?

所以,maximumPoolSize = corePoolSize + 临时工

3、keepAliveTime + 工夫单位用于定义外围线程以外的线程(临时工,如果有的话)的存活工夫,也就是说,这是定义临时工能活多久的参数。

4、ThreadFactory 是线程工厂,用于创立线程。有默认的,也可自定义实现。

5、workQueue 是阻塞队列,也就是临时存工作的中央。

6、Handler 是回绝策略,前面专门有一篇文章来探讨。

理解了这 7 个参数,当初咱们设定 corePoolSize = 5,maximumPoolSize = 10,阻塞队列长度 = 100。再来看一个动图,你对下面的流程图的了解会更深

有哪 6 种线程池,如何应用?

除了自定义,良心的 java 给咱们实现了 3 类,6 个线程池,别离是:

由 ThreadPoolExecutor 创立

  • FixedThreadPool
  • CachedThreadPool
  • SingleThreadExecutor

由 ScheduledThreadPoolExecutor 创立

  • ScheduledThreadPool
  • SingleThreadScheduledExecutor

JDk 1.7 呈现

  • ForkJoinPool(原理较简单,前面再讲)

FixedThreadPool(固定数目的线程池)

public static ExecutorService newFixedThreadPool(int nThreads) {
        return new ThreadPoolExecutor(nThreads, nThreads,
                                      0L, TimeUnit.MILLISECONDS,
                                      new LinkedBlockingQueue<Runnable>());
}
  • 固定线程数的线程池,外围线程数与最大线程数一样。
  • 即便工作数 > 外围线程数,也不会再创立线程,而是扔到队列期待。
  • 队列也满了,那就走回绝策略。
  • 线程闲置,马上回收。

线程数量固定,比拟实用于耗时较长的工作。防止频繁回收和调配线程

执行过程:线程池有 t0 ~ t9 十个线程,他们一直执行工作,期间工作不会缩小不会减少,因为外围线程数 = 最大线程数

用法:用它生成 10 个线程,来执行 10000 个工作:

public class MyThreadPoolTest {public static void main(String[] args) {ExecutorService executorService = Executors.newFixedThreadPool(10);
        for (int i = 0; i < 10000; i++) {executorService.execute(new Thread(new Task()));
        }
        executorService.shutdown();}

    static class Task implements Runnable {public void run() {System.out.println("Thread Name:" + Thread.currentThread().getName());
        }

    }
}

执行后果:能够看到来来去去都是 1~10 这几个线程在跑工作,并没有编号为 11 的线程。

Thread Name: pool-1-thread-1
Thread Name: pool-1-thread-2
Thread Name: pool-1-thread-6
Thread Name: pool-1-thread-8
Thread Name: pool-1-thread-7
Thread Name: pool-1-thread-7
Thread Name: pool-1-thread-1
Thread Name: pool-1-thread-2
Thread Name: pool-1-thread-6
Thread Name: pool-1-thread-10
Thread Name: pool-1-thread-3

CachedThreadPool(可缓存线程的线程池)

上源码:

public static ExecutorService newCachedThreadPool() {
        return new ThreadPoolExecutor(0, Integer.MAX_VALUE,
                                      60L, TimeUnit.SECONDS,
                                      new SynchronousQueue<Runnable>());
    }
  • 外围线程数 = 0,要是始终没工作线程数就 = 0。
  • 最大线程数是有限减少的(最大可到 Integer.MAX_VALUE,为 2^31-1,根本不可能达到)。
  • 线程数并非固定不变,默认闲置线程超过 60s 没工作,则销毁。
  • 队列是 SynchronousQueue 容量是 0,不存储工作,只做直达。

实用于耗时较短的工作、工作处理速度 > 工作提交速度。就不会造成一直创立新线程

用法:用它提交 10000 个工作,并执行。

public class MyThreadPoolTest {public static void main(String[] args) {ExecutorService executorService = Executors.newCachedThreadPool();
        for (int i = 0; i < 10000; i++) {executorService.execute(new Thread(new Task()));
        }
        executorService.shutdown();}

    static class Task implements Runnable {public void run() {System.out.println("Thread Name:" + Thread.currentThread().getName());
        }

    }
}

执行后果:只有有工作提交就新建线程执行

Thread Name: pool-1-thread-826
Thread Name: pool-1-thread-827
Thread Name: pool-1-thread-233
Thread Name: pool-1-thread-303
Thread Name: pool-1-thread-321
Thread Name: pool-1-thread-833
Thread Name: pool-1-thread-825
Thread Name: pool-1-thread-832
Thread Name: pool-1-thread-69
Thread Name: pool-1-thread-18
Thread Name: pool-1-thread-830
Thread Name: pool-1-thread-829

SingleThreadExecutor(单线程的线程池)

源码:

public static ExecutorService newSingleThreadExecutor() {
        return new FinalizableDelegatedExecutorService
            (new ThreadPoolExecutor(1, 1,
                                    0L, TimeUnit.MILLISECONDS,
                                    new LinkedBlockingQueue<Runnable>()));
    }
  • 最大线程数和外围线程都 = 1,有且只有一个线程。

这货有啥应用场景?还真有,比方:用于所有工作都须要按被提交的程序顺次执行的场景

用法:

public class MyThreadPoolTest {public static void main(String[] args) {ExecutorService executorService = Executors.newSingleThreadExecutor();
        for (int i = 0; i < 10000; i++) {executorService.execute(new Thread(new Task()));
        }
        executorService.shutdown();}

    static class Task implements Runnable {public void run() {System.out.println("Thread Name:" + Thread.currentThread().getName());
        }

    }
}

后果:

Thread Name: pool-1-thread-1
Thread Name: pool-1-thread-1
Thread Name: pool-1-thread-1
Thread Name: pool-1-thread-1
Thread Name: pool-1-thread-1
···

ScheduledThreadPool(定时或周期的线程池)

源码:

public static ScheduledExecutorService newScheduledThreadPool(int corePoolSize) {return new ScheduledThreadPoolExecutor(corePoolSize);
}

public ScheduledThreadPoolExecutor(int corePoolSize) {super(corePoolSize, Integer.MAX_VALUE, 0, NANOSECONDS, new DelayedWorkQueue());
}

实用场景:定时或周期性执行工作,它有三个重要的办法:

ScheduledExecutorService service = Executors.newScheduledThreadPool(10);

// 提早指定工夫后执行一次工作(这里是 10s 后执行完工作,完结)service.schedule(new Task(), 10, TimeUnit.SECONDS);

// 以固定的频率执行工作(示意第一次延时后每次延时多长时间执行一次),第二个参数是第一次提早的工夫,第三个参数是周期
service.scheduleAtFixedRate(new Task(), 10, 10, TimeUnit.SECONDS);

// 相似于第二个,区别在于周期的定义。第二个办法的周期是以工作开始工夫为起始工夫计时,而这个是以工作完结的工夫为起始工夫
service.scheduleWithFixedDelay(new Task(), 10, 10, TimeUnit.SECONDS);

用法:

public class MyThreadPoolTest {public static void main(String[] args) {ScheduledExecutorService executorService = Executors.newScheduledThreadPool(10);
        for (int i = 0; i < 10000; i++) {executorService.schedule(new Thread(new Task()), 10, TimeUnit.SECONDS);
        }
        executorService.shutdown();}

    static class Task implements Runnable {public void run() {System.out.println("Thread Name:" + Thread.currentThread().getName());
        }

    }
}

SingleThreadScheduledExecutor(定时或周期的单线程线程池)

源码:

public static ScheduledExecutorService newSingleThreadScheduledExecutor() {
    return new DelegatedScheduledExecutorService
            (new ScheduledThreadPoolExecutor(1));
}

public ScheduledThreadPoolExecutor(int corePoolSize) {
    super(corePoolSize, Integer.MAX_VALUE, 0, NANOSECONDS,
              new DelayedWorkQueue());
}

SingleThreadScheduledExecutor 只有一个线程且反对定时、周期性能。很显著是 ScheduledThreadPool 和 SingleThreadExecutor 的结合体。实用于 对执行程序有要求,且须要定时或周期执行的工作

用法:

public class MyThreadPoolTest {public static void main(String[] args) {ScheduledExecutorService executorService = Executors.newSingleThreadScheduledExecutor();
        for (int i = 0; i < 10000; i++) {executorService.schedule(new Thread(new Task()), 10, TimeUnit.SECONDS);
        }
        executorService.shutdown();}

    static class Task implements Runnable {public void run() {System.out.println("Thread Name:" + Thread.currentThread().getName());
        }

    }
}

伟人的肩膀

  • https://kaiwu.lagou.com/course/courseInfo.htm?courseId=59#/detail/pc?id=1762

总结

本文聊了聊线程池是什么?为什么?怎么用?以及剖析了线程池的执行过程,各参数含意、Java 各线程池的应用以及应用场景。置信你看完会有所播种,当然,因为篇幅起因,阻塞队列、回绝策略等参数前面再发文探讨。另外,如文章有错,请友善斧正,感激不尽。

小福利

如果看到这里,喜爱这篇文章的话,请帮点个难看。微信搜寻 一个优良的废人 ,关注后回复 电子书 送你 1000+ 本编程电子书,不只 Java 哦,详情看下图。回复 1024送你一套残缺的某课网 java 待业班视频教程。

正文完
 0