JAVA线程池的建立与使用

  • 为何要用线程池?

咱们都知道,每一次建立一个线程,JVM后面的工做包括:为线程创建虚拟机栈、本地方法栈、程序计数器的内存空间(下图可看出),因此线程过多容易致使内存空间溢出。同时,当频繁的建立和销毁线程容易浪费系统的计算能力在资源的回收和申请中。缓存

 

 

另外:建立过多的线程,会致使cpu在线程中的切换时间比处理时间还多,大大下降了系统的吞吐量。所以咱们使用线程池以下好处:安全

  1. 有效控制线程的数量,防止线程数量过多。
  2. 提升线程的利用程度,避免频繁的建立及销毁线程。
  3. 有更灵活的线程使用方式及拒绝措施。

再给你们看看阿里开发规约里面是怎么说的并发

 

  • 线程的快速示例

我知道大多数人都但愿先看看线程池怎么建立,而后再深刻了解。下面给你们一个demoide

 1         //存听任务的阻塞队列
 2         BlockingDeque<Runnable> queue = new LinkedBlockingDeque<>(10);
 3         //BasicThreadFactory是本身实现ThreadFactory接口而来
 4         BasicThreadFactory factory = new BasicThreadFactory();
 5         ThreadPoolExecutor poolExecutor = new ThreadPoolExecutor(3, 10, 60,
 6                 TimeUnit.SECONDS, queue, factory,
 7                 (Runnable r, ThreadPoolExecutor executor)->{
 8                         System.out.println(executor.getQueue().size()+"消息队列已满");
 9                         System.out.println("拒绝服务");
10 
11                 });
ThreadPoolDemo

 

  • 线程池相关概念

  1. 核心线程:若线程池中的线程标记为核心线程,即便核心线程没有运行任务,它也不会被销毁,会一直存在于线程池中,直至线程池被shutdown。
  2. 非核心线程:当线程池中没有空闲的核心线程时,线程池会建立一个非核心线程,而且非核心线程的必定时间内处于空闲状态的时候,非核心线程会被销毁。
  3. 阻塞队列:阻塞队列是当线程池中的没有能用于处理任务的线程时,会把该任务放入阻塞队列,待有能用于处理的线程时,把任务从队列取出处理,阻塞队列的长度能够设置。
  4. 拒绝服务处理:当线程池中的没有线程能提供处理,而且阻塞队列的空间已满,此时会触发拒绝服务异常,开发人员能够根据本身的需求定制不一样的处理策略。

 

  • 建立线程池的7个参数

通常咱们推荐使用ThreadPoolExecutor()自定义建立线程池,由于这比较灵活切可控。函数

  1. int corePoolSize  核心线程数,即肯定有多少个核心线程。
  2. int maximumPoolSize  最大线程数,即限定线程池中的最大线程数量。
  3. long keepAliveTime  非核心线程的存活时间,配合下面的TimeUnit参数肯定时间。
  4. TimeUnit unit  一个时间类型的枚举类。有从纳秒到天的时间量度,配合上面的keepAliveTime肯定非核心线程的存活时间。
  5. BlockingQueue<Runnable> workQueue   装载Runnable的阻塞队列,具体类型能够本身肯定。
  6. ThreadFactory threadFactory  线程工厂,这是一个函数式接口,里面只定义了一个newThread(Runnable task)方法,须要本身实现工厂的方法,在这里咱们能够对线程进行自定义的初始化,例如给线程设定名字,这样方便后期的调试。
  7. RejectedExecutionHandler handler   拒绝服务处理,这也是一个函数式接口,咱们须要实现rejectedExecution(Runnable r, ThreadPoolExecutor executor)这个方法,这里能够根据需求自定义你但愿在处理逻辑。固然Java里面也有已经定义好的四种策略静态类。能够经过ThreadPoolExecutor调用

 

  • Executors中实现的线程池类型

下面介绍的线程池类型,是Jdk帮咱们制定好的策略。可是,有的线程池类型中,要么存在线程数量无限制、要么存在阻塞队列长度无限制,可是这些应该在开发中避免,由于一旦并发太高,会致使大量的对象积压,致使JVM内存溢出。spa

写在前面:jdk提供了默认的工厂方法和默认的默认的拒绝处理策略。线程

默认拒绝策略是:不执行并抛出异常设计

默认的工厂方法是:对线程进行安全检查并命名。调试

 1     static class DefaultThreadFactory implements ThreadFactory {
 2         private static final AtomicInteger poolNumber = new AtomicInteger(1);
 3         private final ThreadGroup group;
 4         private final AtomicInteger threadNumber = new AtomicInteger(1);
 5         private final String namePrefix;
 6 
 7         DefaultThreadFactory() {
 8             SecurityManager s = System.getSecurityManager();
 9             group = (s != null) ? s.getThreadGroup() :
10                                   Thread.currentThread().getThreadGroup();
11             namePrefix = "pool-" +
12                           poolNumber.getAndIncrement() +
13                          "-thread-";
14         }
15 
16         public Thread newThread(Runnable r) {
17             Thread t = new Thread(group, r,
18                                   namePrefix + threadNumber.getAndIncrement(),
19                                   0);
20             if (t.isDaemon())
21                 t.setDaemon(false);
22             if (t.getPriority() != Thread.NORM_PRIORITY)
23                 t.setPriority(Thread.NORM_PRIORITY);
24             return t;
25         }
26     }
defaultFactory

 

 

  1. FixedThreadPool       固定核心线程的线程池。

特色:它的核心线程数量就是最大线程数,因此线程池内的线程永远不会消亡,它采用了无参数的链表阻塞队列,最大的任务数可达232-1个。所以存在任务积压致使内存溢出的风险code

   2.  CachedThreadPool   缓存线程池

特色:没有核心线程,线程池不能知足任务运行时会建立新的线程,线程数量没有上限。默认的消亡时间为60秒。值得注意的是:它的阻塞队列是SynchronousQueue,这是一个没有存储性质的阻塞队列,它的取值操做和放入操做必须是互斥的。根据源码文档的解释,能够理解为每当有任务放入时会当即有线程将它取出执行。

  3.  ScheduledThreadPool  固定调度线程池

特色:有固定的核心线程,线程的数量没有限制,默认存活时间为60秒。同时支持定时及周期性任务执行

  4. SingleThreadExecutor  单核心线程池

特色:只有一个核心线程,因此能保证任务的串行化执行。

  5. WorkStealingPool  并行执行线程池

特色:在jdk8中实现 线程池。它内部的线程池实现是ForkJoinPool,这是一个能够同时利用多个线程来执行任务的线程池。无参默认使用CPU数量的线程数执行任务,因为这个线程池比较复杂,下次专门写一篇博文用于更新。

 

  • 线程池的调用流程

须要注意的是:线程池设计的流程是先利用核心线程处理、核心线程不能处理即把它放入阻塞队列,最好才建立线程来执行任务,直到新建线程也失败才调用拒绝服务处理。

试着理解一下这样设计的好处。能够看到,建立线程永远不是最早想到的办法,线程池尽可能避免建立线程。由于建立线程须要调用全局锁来肯定线程的正确建立,同时也由于线程建立和销毁也须要消耗资源,因此这种方式在最大努力的避免这种状况的发生。

 

  • 线程池的关闭

虽然在实际的开发中,线程池通常是随着项目的部署一块儿存活的,不会常常关闭,可是仍是须要了解如何关闭,怎么关闭比较安全。

线程池可经过调用线程池的shutdownshutdownNow方法来关闭线程池.
它们的原理是遍历线程池中的工做线程,而后逐个调用线程的interrupt方法来中断线程,因此没法响应中断的任务可能永远没法终止.
可是它们存在必定的区别

  • shutdownNow首先将线程池的状态设置成STOP,而后尝试中止全部的正在执行或暂停任务的线程,并返回等待执行任务的列表
  • shutdown只是将线程池的状态设置成SHUTDOWN状态,而后中断全部没有正在执行任务的线程.

只要调用了这两个关闭方法中的任意一个,isShutdown方法就会返回true.
当全部的任务都已关闭后,才表示线程池关闭成功,这时调用isTerminaed方法会返回true.
至于应该调用哪种方法,应该由提交到线程池的任务的特性决定,一般调用shutdown方法来关闭线程池,若任务不必定要执行完,则能够调用shutdownNow方法.

线程关闭的方法转载于做者:全网搜索关注JavaEdge
连接:https://www.nowcoder.com/discuss/152050?type=0&order=0&pos=6&page=0

相关文章
相关标签/搜索