咱们都知道,每一次建立一个线程,JVM后面的工做包括:为线程创建虚拟机栈、本地方法栈、程序计数器的内存空间(下图可看出),因此线程过多容易致使内存空间溢出。同时,当频繁的建立和销毁线程容易浪费系统的计算能力在资源的回收和申请中。缓存
另外:建立过多的线程,会致使cpu在线程中的切换时间比处理时间还多,大大下降了系统的吞吐量。所以咱们使用线程池以下好处:安全
再给你们看看阿里开发规约里面是怎么说的并发
我知道大多数人都但愿先看看线程池怎么建立,而后再深刻了解。下面给你们一个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 });
通常咱们推荐使用ThreadPoolExecutor()自定义建立线程池,由于这比较灵活切可控。函数
int corePoolSize 核心线程数,即肯定有多少个核心线程。
int maximumPoolSize 最大线程数,即限定线程池中的最大线程数量。
long keepAliveTime 非核心线程的存活时间,配合下面的TimeUnit参数肯定时间。
TimeUnit unit 一个时间类型的枚举类。有从纳秒到天的时间量度,配合上面的keepAliveTime肯定非核心线程的存活时间。
BlockingQueue<Runnable> workQueue 装载Runnable的阻塞队列,具体类型能够本身肯定。
ThreadFactory threadFactory 线程工厂,这是一个函数式接口,里面只定义了一个newThread(Runnable task)方法,须要本身实现工厂的方法,在这里咱们能够对线程进行自定义的初始化,例如给线程设定名字,这样方便后期的调试。
RejectedExecutionHandler handler 拒绝服务处理,这也是一个函数式接口,咱们须要实现rejectedExecution(Runnable r, ThreadPoolExecutor executor)这个方法,这里能够根据需求自定义你但愿在处理逻辑。固然Java里面也有已经定义好的四种策略静态类。能够经过ThreadPoolExecutor调用
下面介绍的线程池类型,是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 }
特色:它的核心线程数量就是最大线程数,因此线程池内的线程永远不会消亡,它采用了无参数的链表阻塞队列,最大的任务数可达232-1个。所以存在任务积压致使内存溢出的风险。code
2. CachedThreadPool 缓存线程池
特色:没有核心线程,线程池不能知足任务运行时会建立新的线程,线程数量没有上限。默认的消亡时间为60秒。值得注意的是:它的阻塞队列是SynchronousQueue,这是一个没有存储性质的阻塞队列,它的取值操做和放入操做必须是互斥的。根据源码文档的解释,能够理解为每当有任务放入时会当即有线程将它取出执行。
3. ScheduledThreadPool 固定调度线程池
特色:有固定的核心线程,线程的数量没有限制,默认存活时间为60秒。同时支持定时及周期性任务执行。
4. SingleThreadExecutor 单核心线程池
特色:只有一个核心线程,因此能保证任务的串行化执行。
5. WorkStealingPool 并行执行线程池
特色:在jdk8中实现 线程池。它内部的线程池实现是ForkJoinPool,这是一个能够同时利用多个线程来执行任务的线程池。无参默认使用CPU数量的线程数执行任务,因为这个线程池比较复杂,下次专门写一篇博文用于更新。
须要注意的是:线程池设计的流程是先利用核心线程处理、核心线程不能处理即把它放入阻塞队列,最好才建立线程来执行任务,直到新建线程也失败才调用拒绝服务处理。
试着理解一下这样设计的好处。能够看到,建立线程永远不是最早想到的办法,线程池尽可能避免建立线程。由于建立线程须要调用全局锁来肯定线程的正确建立,同时也由于线程建立和销毁也须要消耗资源,因此这种方式在最大努力的避免这种状况的发生。
虽然在实际的开发中,线程池通常是随着项目的部署一块儿存活的,不会常常关闭,可是仍是须要了解如何关闭,怎么关闭比较安全。
线程池可经过调用线程池的shutdown或shutdownNow方法来关闭线程池.
它们的原理是遍历线程池中的工做线程,而后逐个调用线程的interrupt方法来中断线程,因此没法响应中断的任务可能永远没法终止.
可是它们存在必定的区别
只要调用了这两个关闭方法中的任意一个,isShutdown方法就会返回true.
当全部的任务都已关闭后,才表示线程池关闭成功,这时调用isTerminaed方法会返回true.
至于应该调用哪种方法,应该由提交到线程池的任务的特性决定,一般调用shutdown方法来关闭线程池,若任务不必定要执行完,则能够调用shutdownNow方法.
线程关闭的方法转载于做者:全网搜索关注JavaEdge
连接:https://www.nowcoder.com/discuss/152050?type=0&order=0&pos=6&page=0