线程池的学习与深入研究
来源:互联网 发布:stc单片机引脚定义 编辑:程序博客网 时间:2024/05/18 02:35
对于线程池的认识还一直只是停留在基础会用的状态上,所以想抽出时间来研究一下线程池,再这写一篇文章记录一下学习的情况。
线程池的介绍:
我们经常会使用一个Runnable或者是Thread来构造一个线程,但是如果我们在我们的项目中过多的时候上述的构造方式,在管理线程上就会变得非常棘手,如果并发的线程数量很多,并且每个线程都是执行一个时间很短的任务就结束了,这样频繁创建线程就会大大降低系统的效率,因为频繁创建线程和销毁线程需要时间。
为了弥补上述的缺陷,java中引入了线程池的概念:
线程池是一种多线程处理形式,处理过程中将任务添加到队列,然后在创建线程后自动启动这些任务。线程池线程都是后台线程。每个线程都使用默认的堆栈大小,以默认的优先级运行,并处于多线程单元中。如果某个线程在托管代码中空闲(如正在等待某个事件),则线程池将插入另一个辅助线程来使所有处理器保持繁忙。如果所有线程池线程都始终保持繁忙,但队列中包含挂起的工作,则线程池将在一段时间后创建另一个辅助线程但线程的数目永远不会超过最大值。超过最大值的线程可以排队,但他们要等到其他线程完成后才启动。(来自百度百科)
线程池的分类:
在java中常用的线程池有:
1.FixedThreadPool
我们可以通过Excutors的newFixedThreadPool来进行创建,源代码中的代码如下:
public static ExecutorService newFixedThreadPool(int nThreads) { return new ThreadPoolExecutor(nThreads, nThreads, 0L, TimeUnit.MILLISECONDS, new LinkedBlockingQueue<Runnable>()); }
这是一种线程数量固定的线程池,当线程处于空闲状态时候,他们并不会被回收,除非线程池被关闭了,当所有线程都处于活动状态的时候,新任务会处于一种等待的状态,知道有空闲的线程使用。
这种线程池只拥有核心线程并且不会被回收,意味着它能够更好的响应外界的请求。
2.CachedThreadPool
通过Executors.newCachedThreadPool创建,源代码如下:
public static ExecutorService newCachedThreadPool() { return new ThreadPoolExecutor(0, Integer.MAX_VALUE, 60L, TimeUnit.SECONDS, new SynchronousQueue<Runnable>()); }
这是一种线程数量不固定的线程池,只有非核心线程,线程最大数为Integer.MAX_VALUE,相当于无限大。当线程池中的所有线程度都处于活动状态的时候,线程池会新建线程来达到新任务执行的过程,否则会使用线程池中的线程。
这种线程适合执行大量耗时较少的任务,当整个线程池都处于闲置状态的时候,线程池中的县城都会超市而停止,这个时线程池几乎不占任何系统资源。
3.ScheduledThreadPool
通过 Executors.newScheduledThreadPool()创建,源代码如下所示:
public static ScheduledExecutorService newScheduledThreadPool( int corePoolSize, ThreadFactory threadFactory) { return new ScheduledThreadPoolExecutor(corePoolSize, threadFactory); }
这种线程池的核心线程是被固定的,而非核心线程则没有限制,且当核心线程闲置时会被回收。
4.SingleThreadPool
由Executors.newSingleThreadPool来创建,源代码展示如下:
public static ExecutorService newSingleThreadExecutor() { return new FinalizableDelegatedExecutorService (new ThreadPoolExecutor(1, 1, 0L, TimeUnit.MILLISECONDS, new LinkedBlockingQueue<Runnable>())); }
这是一个只有单一线程的线程池,所有的任务都会依照队列来排列执行,这个线程池的意义在于让所有外界任务统一到一个线程当中,这使得这些任务不必关注同步的问题。
ThreadPoolExecutor
在上述的线程池中,我们可以看到所有的线程池的来源都来自于Executors类,那我们在这里就来详细的学习一下这个类。
我们点进源码又可以看到其实这是一个相当于工具类的源码,里面进行了各种的线程池的创建的工作,在各种线城池创建中,无一例外的会传入一个ThreadPoolExecutor实例,那我们把重点研究的方向放到这个类中:
ThreadPoolExuctor继承自AbstractExecutorService,在其中处理了线程池的初始化的操作。
构造方法:
public ThreadPoolExecutor(int corePoolSize, int maximumPoolSize, long keepAliveTime, TimeUnit unit, BlockingQueue<Runnable> workQueue, ThreadFactory threadFactory, RejectedExecutionHandler handler) { if (corePoolSize < 0 || maximumPoolSize <= 0 || maximumPoolSize < corePoolSize || keepAliveTime < 0) throw new IllegalArgumentException(); if (workQueue == null || threadFactory == null || handler == null) throw new NullPointerException(); this.corePoolSize = corePoolSize; this.maximumPoolSize = maximumPoolSize; this.workQueue = workQueue; this.keepAliveTime = unit.toNanos(keepAliveTime); this.threadFactory = threadFactory; this.handler = handler; }
上述代码中的参数的含义:
1.corePoolSize:核心池的大小
2.maximumPoolSize:最大线程池的大小
3.keepAliveTime:线程存活时间
4.workQueue:维护线程的队列
5.threadFactory:当线程创建时调用的工厂方法
6.handler:处理当线程超过线程池最大线程数量的工作对象。
我们在使用一个线程池的时候会调用execute()方法,下面我们就来看下这个方法:
public void execute(Runnable command) { if (command == null) throw new NullPointerException(); 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); }
这个方法主要的作用是进行线程池的相关检查操作:
如果线程池中的线程数量小于核心池中的线程数量,则新启动一个线程执行任务。
如果线程池中的线程数量大于核心池中的线程数量,则将线程添加进队列当中。
- 如果队列当中的线程数量也满了,则会新开一个线程,如果尝试失败,则会调用,handler.rejectedExecution抛出异常。
我们再看看addWorker这个方法:
private boolean addWorker(Runnable firstTask, boolean core) { retry: for (;;) { int c = ctl.get(); int rs = runStateOf(c); // Check if queue empty only if necessary. if (rs >= SHUTDOWN && ! (rs == SHUTDOWN && firstTask == null && ! workQueue.isEmpty())) return false; for (;;) { int wc = workerCountOf(c); if (wc >= CAPACITY || wc >= (core ? corePoolSize : maximumPoolSize)) return false; if (compareAndIncrementWorkerCount(c)) break retry; c = ctl.get(); // Re-read ctl if (runStateOf(c) != rs) continue retry; // else CAS failed due to workerCount change; retry inner loop } } boolean workerStarted = false; boolean workerAdded = false; Worker w = null; try { w = new Worker(firstTask); final Thread t = w.thread; if (t != null) { final ReentrantLock mainLock = this.mainLock; mainLock.lock(); try { // Recheck while holding lock. // Back out on ThreadFactory failure or if // shut down before lock acquired. int rs = runStateOf(ctl.get()); if (rs < SHUTDOWN || (rs == SHUTDOWN && firstTask == null)) { if (t.isAlive()) // precheck that t is startable throw new IllegalThreadStateException(); workers.add(w); int s = workers.size(); if (s > largestPoolSize) largestPoolSize = s; workerAdded = true; } } finally { mainLock.unlock(); } if (workerAdded) { t.start(); workerStarted = true; } } } finally { if (! workerStarted) addWorkerFailed(w); } return workerStarted; }
上述方法主要是检查一个线程是否能被加入的当前的线程池当中,如果可以工作线程数量则会进行调整,如果可能的话,把当前传入的线程作为第一个任务进行执行操作。
我们可以再看看worker类这个方法:
private final class Worker extends AbstractQueuedSynchronizer implements Runnable { /** * This class will never be serialized, but we provide a * serialVersionUID to suppress a javac warning. */ private static final long serialVersionUID = 6138294804551838833L; /** Thread this worker is running in. Null if factory fails. */ final Thread thread; /** Initial task to run. Possibly null. */ Runnable firstTask; /** Per-thread task counter */ volatile long completedTasks; /** * Creates with given first task and thread from ThreadFactory. * @param firstTask the first task (null if none) */ Worker(Runnable firstTask) { setState(-1); // inhibit interrupts until runWorker this.firstTask = firstTask; this.thread = getThreadFactory().newThread(this); } /** Delegates main run loop to outer runWorker. */ public void run() { runWorker(this); } // Lock methods // // The value 0 represents the unlocked state. // The value 1 represents the locked state. protected boolean isHeldExclusively() { return getState() != 0; } protected boolean tryAcquire(int unused) { if (compareAndSetState(0, 1)) { setExclusiveOwnerThread(Thread.currentThread()); return true; } return false; } protected boolean tryRelease(int unused) { setExclusiveOwnerThread(null); setState(0); return true; } public void lock() { acquire(1); } public boolean tryLock() { return tryAcquire(1); } public void unlock() { release(1); } public boolean isLocked() { return isHeldExclusively(); } void interruptIfStarted() { Thread t; if (getState() >= 0 && (t = thread) != null && !t.isInterrupted()) { try { t.interrupt(); } catch (SecurityException ignore) { } } } }
可以看到这是个一个继承线程的内部类,我们主要看他的run方法,从run方法的实现可以看出,它首先执行的是通过构造器传进来的任务firstTask,在调用runTask()执行完firstTask之后,在while循环里面不断通过getTask()去取新的任务来执行,那么去哪里取呢?自然是从任务缓存队列里面去取,getTask是ThreadPoolExecutor类中的方法,并不是Worker类中的方法,下面是getTask方法的实现:
private Runnable getTask() { boolean timedOut = false; // Did the last poll() time out? for (;;) { int c = ctl.get(); int rs = runStateOf(c); // Check if queue empty only if necessary. if (rs >= SHUTDOWN && (rs >= STOP || workQueue.isEmpty())) { decrementWorkerCount(); return null; } int wc = workerCountOf(c); // Are workers subject to culling? boolean timed = allowCoreThreadTimeOut || wc > corePoolSize; if ((wc > maximumPoolSize || (timed && timedOut)) && (wc > 1 || workQueue.isEmpty())) { if (compareAndDecrementWorkerCount(c)) return null; continue; } try { Runnable r = timed ? workQueue.poll(keepAliveTime, TimeUnit.NANOSECONDS) : workQueue.take(); if (r != null) return r; timedOut = true; } catch (InterruptedException retry) { timedOut = false; } } }
在getTask中,先判断当前线程池状态,如果runState大于SHUTDOWN(即为STOP或者TERMINATED),则直接返回null。
如果runState为SHUTDOWN或者RUNNING,则从任务缓存队列取任务。
如果当前线程池的线程数大于核心池大小corePoolSize或者允许为核心池中的线程设置空闲存活时间,则调用poll(time,timeUnit)来取任务,这个方法会等待一定的时间,如果取不到任务就返回null。
然后判断取到的任务r是否为null,为null则通过调用workerCanExit()方法来判断当前worker是否可以退出。
总结一下:
1)首先,要清楚corePoolSize和maximumPoolSize的含义;
2)其次,要知道Worker是用来起到什么作用的;
3)要知道任务提交给线程池之后的处理策略,这里总结一下主要有4点:
如果当前线程池中的线程数目小于corePoolSize,则每来一个任务,就会创建一个线程去执行这个任务;
如果当前线程池中的线程数目>=corePoolSize,则每来一个任务,会尝试将其添加到任务缓存队列当中,若添加成功,则该任务会等待空闲线程将其取出去执行;若添加失败(一般来说是任务缓存队列已满),则会尝试创建新的线程去执行这个任务;
如果当前线程池中的线程数目达到maximumPoolSize,则会采取任务拒绝策略进行处理;
如果线程池中的线程数量大于 corePoolSize时,如果某线程空闲时间超过keepAliveTime,线程将被终止,直至线程池中的线程数目不大于corePoolSize;如果允许为核心池中的线程设置存活时间,那么核心池中的线程空闲时间超过keepAliveTime,线程也会被终止。
参考资料:
http://www.cnblogs.com/dolphin0520/p/3932921.html
《android艺术开发探索》
- 线程池的学习与深入研究
- 深入研究线程池
- 深入研究线程池
- 深入研究线程池
- 深入研究线程池
- 深入研究线程池
- 深入研究线程池
- 线程安全的深入研究
- 线程安全的深入研究
- 线程池的深入了解及其实现机制的研究
- 线程池的研究与实现
- 深入研究servlet的线程安全问题
- 深入研究servlet的线程安全问题(转)
- 深入研究servlet的线程安全问题
- 深入研究servlet的线程安全问题(转)
- 深入研究servlet的线程安全问题(转)
- 深入研究servlet的线程安全问题(转)
- 线程池的研究
- 如何查看自己电脑的IP和网络运营商
- C语言高效编程
- 通过hashmap实现统计字符串数组中某个字符串出现的次数的方法
- 要求:通过创建一个货物类,添加货物基本信息,使用TreeMap进行比较输出 方法一为只比较价格;方法二为先比较价格再比较销量然后再输出
- UVA 11093(p246)----Just Finish it up
- 线程池的学习与深入研究
- Emmet的高级功能与使用技巧
- ubuntu14.04 疑难问题整理
- JSP学习笔记一之JSP的结构与生命周期
- UVA 11105(p345)----Semi-prime H-numbers
- ESP8266(二)NODEMCU——烧录固件
- StackOverflow程序员推荐:每个程序员都应读的30本书
- 使用Ambari快速部署Hadoop大数据环境
- UVA 11186(p349)----Circum Triangle