线程池解决了两个不同的问题:
1、通过避免每次都创建新的线程,减少创建时间消耗,提高了大量异步task的处理性能。
2、提供了限定和管理资源(包括线程,task集合)的方法。
在使用线程池时虽然可以通过java.util.concurrent.Executors提供的静态工厂方法获得线程池来使用,但是真正的清楚了其中的实现细节,才能真正的利用起ThreadPoolExecutor,比如自定义线程工厂(ThreadFactory),使用自定义的Thread子类实现将资源绑定到线程上,或者自定义BlockingQueue来控制任务的执行模型。而不是简单的执行一个Runnable对象。
首先比较Executors中的两个工厂方法,
和
newFixedThreadPool 和 newCachedThreadPool 构造出来的都是 ThreadPoolExecutor 对象为何性质有如此大的不同呢,使用的都是
ThreadPoolExecutor(int corePoolSize, int maximumPoolSize,long keepAliveTime,TimeUnit unit,BlockingQueue<Runnable> workQueue,ThreadFactory threadFactory) 构造方法,对比我们发现构造参数有下列不一样的地方。
1、FixedThreadPool需要制定 线程数,而且corePoolSize,和maximumPoolSize的大小是一样的,而CachedThreadPool则分别制定为 0 和无限大。
2、FixedThreadPool设置keepAliveTime为0,而CachedThreadPool设置成60秒。
3、两者使用的BlockingQueue不一样,一个大小没有限制的LinkedBlockingQueue,一个使用了SynchronousQueue,该队列只有在有其他线程进行插入操作的时候才能移除,相反也一样,在生产者消费者模型里,意味了生产的的task必须立即被消费者处理。
首先,我们从execute(Runnable command)方法的实现入手,来了解新任务具体是怎么被执行的。
我们可以了解到
1、当线程数小于 corePoolSize 是,新提交的任务总是以创建新线程的方式运行。
2、当线程数不小于corePoolSize是,新的任务会被放入队列,只有队列满时才会创建新的线程,所以fixedThreadPool使用不限制大小的LinkedBlockingQueue时,maximumPoolSize就不会不会有任何影响,创建的线程数不会多于corePoolSize。
所以比较fixedThreadPool和CacheedThreadPool,最重要的差异是使用的BlockingQueue不同。
SynchronousQueue直接把task交付给线程,而不是把它放在队列里。试图向该队列offer task 时如果没有可用的线程立刻处理将会失败,所以就会创建新的线程,该策略避免了有依赖的线程的死锁,直接交付需要无限大的maximumPoolSizes避免拒绝新提交的task,提交任务比处理任务快的话就意味着,线程无限制的增长,java.util.concurrent.Executors.newCachedThreadPool()就是使用的这种策略。
LinkedBlockingQueue是没有限制大小的队列,如果所有的corePool线程都繁忙的话会使新的task在队列中等待,因此线程数的大小不会超过corePoolSize,maximumPoolSize将不会有任何的影响,这种队列适合没有依赖的task,使用这种队列意味对象可能无限制的增长,导致内存溢出。
接下来的疑问是为什么两者的keepAliveTime和corePoolSize设置不同,在线程池中,默认核心线程是不会超时终止的,如果该线程池没有显式的关闭,只有在线程池不再被引用且线程全部结束才能被回收,所以FixedThreadPool不关闭的话,资源将一直得不到回收,而CachedThreadPool由于没有核心线程,当没有任务执行且不被引用后能够自动的被垃圾回收。
每次新建一个Worker就会调用ThreadFactory新建线程,使用该线程进行如下的循环
可以知道
1、当执行异常抛出时循环会退出(线程退出),这时 completedAbruptly 会记录状态,让 processWorkerExit 会创建新的线程,并且在线程退出时会调用terminated方法。
2、该线程执行task前会调用 beforeExecute ,方法可能会抛出异常,导致当前线程死亡 (breaking loop with completedAbruptly true) 。
3、如果beforeExecute正常执行,接着会执行任务,捕获所有的异常,交给 afterExecute 处理,afterExecute处理抛出异常也会导致线程死亡,
其实这里的beforeExecute ,afterExecute ,terminated方法我们都可以通过继承ThreadPoolExecutor的方法去覆盖,做一些task执行前的准备和校验工作,还能通过afterExecute 获得task执行时发生的异常。
1、通过避免每次都创建新的线程,减少创建时间消耗,提高了大量异步task的处理性能。
2、提供了限定和管理资源(包括线程,task集合)的方法。
在使用线程池时虽然可以通过java.util.concurrent.Executors提供的静态工厂方法获得线程池来使用,但是真正的清楚了其中的实现细节,才能真正的利用起ThreadPoolExecutor,比如自定义线程工厂(ThreadFactory),使用自定义的Thread子类实现将资源绑定到线程上,或者自定义BlockingQueue来控制任务的执行模型。而不是简单的执行一个Runnable对象。
首先比较Executors中的两个工厂方法,
public static ExecutorService newFixedThreadPool(int nThreads, ThreadFactory threadFactory) { return new ThreadPoolExecutor(nThreads, nThreads, 0L, TimeUnit.MILLISECONDS, new LinkedBlockingQueue<Runnable>(), threadFactory); }
和
public static ExecutorService newCachedThreadPool(ThreadFactory threadFactory) { return new ThreadPoolExecutor(0, Integer.MAX_VALUE,60L, TimeUnit.SECONDS, new SynchronousQueue<Runnable>(), threadFactory); }
newFixedThreadPool 和 newCachedThreadPool 构造出来的都是 ThreadPoolExecutor 对象为何性质有如此大的不同呢,使用的都是
ThreadPoolExecutor(int corePoolSize, int maximumPoolSize,long keepAliveTime,TimeUnit unit,BlockingQueue<Runnable> workQueue,ThreadFactory threadFactory) 构造方法,对比我们发现构造参数有下列不一样的地方。
1、FixedThreadPool需要制定 线程数,而且corePoolSize,和maximumPoolSize的大小是一样的,而CachedThreadPool则分别制定为 0 和无限大。
2、FixedThreadPool设置keepAliveTime为0,而CachedThreadPool设置成60秒。
3、两者使用的BlockingQueue不一样,一个大小没有限制的LinkedBlockingQueue,一个使用了SynchronousQueue,该队列只有在有其他线程进行插入操作的时候才能移除,相反也一样,在生产者消费者模型里,意味了生产的的task必须立即被消费者处理。
首先,我们从execute(Runnable command)方法的实现入手,来了解新任务具体是怎么被执行的。
public void execute(Runnable command) { if (command == null) throw new NullPointerException(); /* * Proceed in 3 steps: * * 1. If fewer than corePoolSize threads are running, try to * start a new thread with the given command as its first * task. The call to addWorker atomically checks runState and * workerCount, and so prevents false alarms that would add * threads when it shouldn't, by returning false. * * 2. If a task can be successfully queued, then we still need * to double-check whether we should have added a thread * (because existing ones died since last checking) or that * the pool shut down since entry into this method. So we * recheck state and if necessary roll back the enqueuing if * stopped, or start a new thread if there are none. * * 3. If we cannot queue task, then we try to add a new * thread. If it fails, we know we are shut down or saturated * and so reject the task. */ int c = ctl.get(); if (workerCountOf(c) < corePoolSize) { if (addWorker(command, true)) return; c = ctl.get(); } if (isRunning(c) && workQueue.offer(command)) { int recheck = ctl.get(); if (! isRunning(recheck) && remove(command)) reject(command); else if (workerCountOf(recheck) == 0) addWorker(null, false); } else if (!addWorker(command, false)) reject(command); }
我们可以了解到
1、当线程数小于 corePoolSize 是,新提交的任务总是以创建新线程的方式运行。
2、当线程数不小于corePoolSize是,新的任务会被放入队列,只有队列满时才会创建新的线程,所以fixedThreadPool使用不限制大小的LinkedBlockingQueue时,maximumPoolSize就不会不会有任何影响,创建的线程数不会多于corePoolSize。
所以比较fixedThreadPool和CacheedThreadPool,最重要的差异是使用的BlockingQueue不同。
SynchronousQueue直接把task交付给线程,而不是把它放在队列里。试图向该队列offer task 时如果没有可用的线程立刻处理将会失败,所以就会创建新的线程,该策略避免了有依赖的线程的死锁,直接交付需要无限大的maximumPoolSizes避免拒绝新提交的task,提交任务比处理任务快的话就意味着,线程无限制的增长,java.util.concurrent.Executors.newCachedThreadPool()就是使用的这种策略。
LinkedBlockingQueue是没有限制大小的队列,如果所有的corePool线程都繁忙的话会使新的task在队列中等待,因此线程数的大小不会超过corePoolSize,maximumPoolSize将不会有任何的影响,这种队列适合没有依赖的task,使用这种队列意味对象可能无限制的增长,导致内存溢出。
接下来的疑问是为什么两者的keepAliveTime和corePoolSize设置不同,在线程池中,默认核心线程是不会超时终止的,如果该线程池没有显式的关闭,只有在线程池不再被引用且线程全部结束才能被回收,所以FixedThreadPool不关闭的话,资源将一直得不到回收,而CachedThreadPool由于没有核心线程,当没有任务执行且不被引用后能够自动的被垃圾回收。
每次新建一个Worker就会调用ThreadFactory新建线程,使用该线程进行如下的循环
boolean completedAbruptly = true; try { while (task != null || (task = getTask()) != null) { w.lock(); // If pool is stopping, ensure thread is interrupted; // if not, ensure thread is not interrupted. This // requires a recheck in second case to deal with // shutdownNow race while clearing interrupt if ((runStateAtLeast(ctl.get(), STOP) || (Thread.interrupted() && runStateAtLeast(ctl.get(), STOP))) && !wt.isInterrupted()) wt.interrupt(); try { beforeExecute(wt, task); Throwable thrown = null; try { task.run(); } catch (RuntimeException x) { thrown = x; throw x; } catch (Error x) { thrown = x; throw x; } catch (Throwable x) { thrown = x; throw new Error(x); } finally { afterExecute(task, thrown); } } finally { task = null; w.completedTasks++; w.unlock(); } } completedAbruptly = false; } finally { processWorkerExit(w, completedAbruptly); }
private void processWorkerExit(Worker w, boolean completedAbruptly) { if (completedAbruptly) // If abrupt, then workerCount wasn't adjusted decrementWorkerCount(); final ReentrantLock mainLock = this.mainLock; mainLock.lock(); try { completedTaskCount += w.completedTasks; workers.remove(w); } finally { mainLock.unlock(); } tryTerminate(); int c = ctl.get(); if (runStateLessThan(c, STOP)) { if (!completedAbruptly) { int min = allowCoreThreadTimeOut ? 0 : corePoolSize; if (min == 0 && ! workQueue.isEmpty()) min = 1; if (workerCountOf(c) >= min) return; // replacement not needed } addWorker(null, false); } }
可以知道
1、当执行异常抛出时循环会退出(线程退出),这时 completedAbruptly 会记录状态,让 processWorkerExit 会创建新的线程,并且在线程退出时会调用terminated方法。
2、该线程执行task前会调用 beforeExecute ,方法可能会抛出异常,导致当前线程死亡 (breaking loop with completedAbruptly true) 。
3、如果beforeExecute正常执行,接着会执行任务,捕获所有的异常,交给 afterExecute 处理,afterExecute处理抛出异常也会导致线程死亡,
其实这里的beforeExecute ,afterExecute ,terminated方法我们都可以通过继承ThreadPoolExecutor的方法去覆盖,做一些task执行前的准备和校验工作,还能通过afterExecute 获得task执行时发生的异常。
相关推荐
Java线程池和Executor原理分析 Java线程池和Executor原理是Java并发编程中非常重要的一部分。线程池是一个或者多个线程的集合,用户可以把需要执行的任务简单地扔给线程池,而不用过多的纠结与执行的细节。Java...
Java是一种广泛使用的编程语言,尤其在企业级应用和Web服务开发中占据主导地位。这个"java源码:Java开发的简单WEB服务器源码.rar...通过阅读和分析这些代码,你可以深入学习到Java网络编程的精髓,提升自己的技能。
- **线程池**:ExecutorService、ThreadPoolExecutor和Executors的使用及原理。 5. **内存模型** - **JVM内存区域**:堆、栈、方法区、程序计数器、本地方法栈等。 - **垃圾回收**:GC的工作原理,不同垃圾收集...
通过分析这个"java实现的电风扇"项目,我们可以深入理解Java多线程的原理和实践,这对于开发高并发、实时性强的应用非常有价值。在实际应用中,多线程不仅可以用于模拟物理设备,还可以用于网络请求、定时任务、...
10. 算法与数据结构基础知识:冒泡、选择、插入、查找算法之二分法、局部最小值、时间复杂度与常见时间复杂度列表、对数器与关于随机的简单题目、数据结构的基本概念、连续结构与跳转结构、哈希表、单链表与简单题目...
通过分析和实践Java Chat 项目,初学者可以深入了解Java网络编程的基本原理,以及如何利用多线程、数据序列化等技术实现一个简单的实时通信应用。这将为他们进一步学习复杂的分布式系统和大型网络应用打下坚实的基础...
8. **线程池**:探讨线程池的工作原理和最佳实践,如`ThreadPoolExecutor`的配置和使用。 接下来,转向网络高级编程。Java在网络编程方面提供了丰富的API,使得开发网络应用变得简单。《Java网络高级编程》可能涵盖...
- 垃圾回收:理解垃圾收集机制,包括可达性分析算法和垃圾回收器的工作原理。 - 分代收集:了解新生代、老年代的划分,以及Minor GC和Major GC。 3. **类和对象** - 构造器:了解构造函数的作用和重载。 - 抽象...
15. HTTP协议:理解HTTP的工作原理,构建简单的HTTP客户端和服务端。 七、反射与注解 16. 反射:理解反射机制,如何动态获取类信息,创建和调用对象方法。 17. 注解:学习注解的使用,自定义注解,以及元注解的...
- **多人聊天程序**:综合运用Java集合、文件操作、网络通信、多线程等技术实现一个简单的多人聊天室,加深对Java核心技术的理解,并实践设计模式的应用。 通过以上内容的学习,学员能够全面掌握Java语言的基础知识...
- **垃圾收集**:理解Java垃圾收集机制,包括GC的工作原理和几种常见的垃圾收集器。 - **内存区域**:堆、栈、方法区、程序计数器、本地方法栈的作用及生命周期。 7. **JVM优化**: - **JVM运行参数**:如-Xms、...
5. **网络编程**:Java的Socket编程、ServerSocket、URL、URLConnection等用于网络通信,面试中可能要求编写简单的客户端和服务端程序,或者解释TCP/IP协议的工作原理。 6. **I/O流**:Java的I/O系统包括字节流、...
这个简单的Java Web服务器源码提供了一个学习和实践的基础平台,可以帮助开发者理解Web服务器的工作原理,进一步深入到Java网络编程和HTTP协议的细节。通过阅读和修改源码,开发者可以学习如何从头开始构建自己的Web...
首先,我们需要了解Java爬虫的基本原理。Java爬虫通常由以下几个关键部分组成: 1. **网络请求**:使用Java的HttpURLConnection或者第三方库如Apache HttpClient或OkHttp来发送HTTP请求,获取网页内容。在这个项目...
【Java达内面试题库】是专门为准备Java程序员面试者设计的一份综合性的题库,旨在帮助应聘者全面了解和掌握Java技术领域的核心知识点。这份题库覆盖了Java语言的基础到高级各个方面,对于想要在面试中脱颖而出的Java...
3. **更完善的线程池API**:ExecutorService和ThreadPoolExecutor是Java 7中管理线程的主要工具。通过定制线程池,可以有效地控制并发程度,避免资源浪费,提高系统效率。ScheduledExecutorService则支持定时及周期...
通过调试和运行这些代码,初学者可以直观地理解Java爬虫的工作原理,同时锻炼解决问题的能力。例如,可能会遇到的挑战有反爬机制、动态加载内容、数据解析等问题,这些都是在实际爬虫开发中常见的问题。 5. **NBA...
### Java性能调优知识点概述 ...Java性能调优是一个复杂且细致的过程,涉及到对Java语言特性的深入了解以及对并发编程原理的掌握。通过上述内容的学习,开发者可以更好地理解如何编写出既高效又稳定的Java应用程序。
【标题】"简单的JAVA多线程下载"是一个适合Java初学者学习的主题,它涉及的是如何在Java编程中利用多线程技术实现文件的并行下载。多线程是Java编程中的一个重要概念,允许程序同时执行多个任务,提高程序的效率和...