对象池和线程池
来源:互联网 发布:中华人软件下载 编辑:程序博客网 时间:2024/06/05 19:33
实际开发中,并发和多线程的情景处处存在。今天我们来聊聊对象池和线程池这个两个日常开发用的比较多,但是又很容易混淆的两个概念。
对象池
首先我们来说说对象池。对象池利用了串行封闭的概念:将对象O“借给”一个请求线程T1,T1使用完之后再交还给对象池,并保证“未擅自发布该对象”且“以后不再使用”;对象池收回对象O之后,等T2来借的时候再把他借给T2,完成对象所用权的传递。
下面是一个简化版的对象池
public abstract class AbstractObjectPool<T> { protected final int min; protected final int max; protected final List<T> usings = Lists.newLinkedList(); protected final List<T> buffer = Lists.newLinkedList(); private volatile boolean inited = false; public AbstractObjectPool(int min, int max) { this.min = min; this.max = max; if (this.min < 0 || this.min > this.max) { throw new IllegalArgumentException(String.format("need 0<=min<=max<=Integer.MAX_VALUE,given min%s,max:%s", this.min, this.max)); } } public void init() { for (int i = 0; i < min; i++) { buffer.add(newObject()); } inited = true; } public void checkInited() { if (!inited) { throw new IllegalStateException("not inited"); } } abstract protected T newObject(); public synchronized T getObject() { checkInited(); if (usings.size() == max) { return null; } if (buffer.size() == 0) { T newObject = newObject(); usings.add(newObject); return newObject; } T oldObject = buffer.remove(0); usings.add(oldObject); return oldObject; } public synchronized void freeObject(T object) { checkInited(); if (!usings.contains(object)) { throw new IllegalArgumentException(String.format("object not in using queue:%s", object)); } usings.remove(usings.indexOf(object)); buffer.add(object); }}
AbstractObjectPool具有以下特性:
- 支持设置最小最大容量
- 对象一旦申请就不再释放,避免了GC
虽然简单,但是大可以用于一些时间敏感、资源充裕的场景。如果时间进一步敏感,可将getObject()、freeObject()改写为并发程度更高的版本,但是记得得保证安全发布安全回收;如果资源不那么充裕,可以适当增加对象的回收策略。
可以看到,一个对象池的基本行为包括:
- 创建对象newObject()
- 借取对象getObject()
- 归还对象freeObject()
典型的对象池有各种连接池(如数据库连接池)、常量池等。那对象池和线程是之间有什么区别呢,线程池的核心原理是不是对象池,下面我们来追溯一下java源码。
线程池
首先摆出结论:线程池糅合了对象池模型,但是核心原理是生产者-消费者模型。
继承结构如下:
用户可以将Runnable(或Callables)实例提交给线程池,线城池会异步执行该任务,返回响应的结果(完成/返回值)。
我最喜欢的是submit(Callable<T> task)方法。我们从该方法入手,逐步深入函数栈,探究线程池的实现原理。
submit()
submit()方法在ExecutorService接口中定义,在抽象类AbstractExecutorService实现,ThreadPoolExecutor直接继承。
public abstract class AbstractExecutorService implements ExecutorService { ... /** * @throws RejectedExecutionException {@inheritDoc} * @throws NullPointerException {@inheritDoc} */ public <T> Future<T> submit(Callable<T> task) { if (task == null) throw new NullPointerException(); RunnableFuture<T> ftask = newTaskFor(task); execute(ftask); return ftask; }}
AbstractExecutorService #newTaskFor()创建一个RunnableFutrue类型的FutureTask。核心是execute()方法
execute()方法
execute()方法在接口Executor定义,在ThreadPoolExecutor实现。
public class ThreadPoolExecutor extends AbstractExecutorService { ... 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); } ...}
我们暂且忽略线程池的池化策略。关注一个最简单的场景,看看线程池中任务是怎样执行的。
核心是addWorker()方法。以第8行为例,此时,线程池中线程数未达到最小线程池的大小corePoolSize,通常可以直接在第9行返回。
addWorker()
addWorker()方法是ThreadPoolExecutor的私有方法。
public class ThreadPoolExecutor extends AbstractExecutorService { .... private boolean addWorker(Runnable firstTask, boolean core) { ... 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 { int rs = runStateOf(ctl.get()); if (rs < SHUTDOWN){ workers.add(w); workerAdded = true; } } finally { mainLock.unlock(); } if (workerAdded) { t.start(); workerStarted = true; } } } finally { if (! workerStarted) addWorkerFailed(w); } return workerStarted; } ....}
这里去掉了很多用于管理线程池、维护线程安全的代码。假设线程池未关闭,worker(即w,下同)添加成功,则必然将worker添加至workers中。workers是一个HashSet:
/** * Set containing all worker threads in pool. Accessed only when * holding mainLock. */ private final HashSet<Worker> workers = new HashSet<Worker>();
哪里是对象池
如果说与对象池有关,那么只有workers及相当于对象池实例代码中的using,应用了对象池模型;只不过这里的using是一直增长的,直到打到最大线程池的大小maximumPoolSize。
但是很明显,线程池并没有将线程发布出去,workers仅仅是完成using保存线程的作用。那么线程池中的任务是怎样执行的呢?跟线程池有没有关系呢?
哪里又不是对象池?
注意上面addWorker()代码中的第9、17、24行代码
- 9行将我们提交到线程池的firstTask封装入一个worker.
- 17行将worker加入到workers维护起来
- 24行启动了worker中的线程t
核心就在这三行,线程池没有直接在addWorker()中启动我们传进来的firstTask,代之以启动 一个worker,最终任务必然被启动,那么我们继续看Worker是怎样启动我们这个任务的。
Worker
Worker实现了Runnable接口:
private final class Worker extends AbstractQueuedSynchronizer implements Runnable{ ... /** * 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); } ...}
为什么要将构造Worker时的参数命名为firstTask?因为当且仅当需要建立新的Worker以执行任务task时,才会调用构造函数。因此,任务task对于新Worker而言,是第一个任务firstTask。
Worker的实现非常简单:将自己作为Runable实例,构造时在内部创建并持有一个线程thread。Thread和Runable的使用大家很熟悉了,核心是Worker的run方法,它直接调用了runWorker()方法。
runWorker()
重点来了。简化如下:
public class ThreadPoolExecutor extends AbstractExecutorService { ... final void runWorker(Worker w) { Thread wt = Thread.currentThread(); Runnable task = w.firstTask; w.firstTask = null; w.unlock(); // allow interrupts boolean completedAbruptly = true; try { while (task != null || (task = getTask()) != null) { w.lock(); 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); } } ...}
我们在前面将要执行的任务赋值给firstTask,5-6行首先取出任务task,并将firstTask置为null。因为接下来要执行task,firstTask字段就没有用了。
重点是10-31行的while循环。下面分情况讨论。
case1:第一次进入循环,task不为null
case1对应前面所有假设,程序正常运行进入到这个while循环。
第一次进入循环时,task==firstTask,不为null,直接进入循环体;从而执行16行的firstTask的run()方法;
异常处理我们在这里先不考虑;最后,在finally代码块中,task被置为null,导致下一轮循环会进入case2。
case2:非第一次进入循环体,task为null
case2是更为普遍的情况,是线程池的核心。
case1中,task被置为null,使得第10行的布尔表达式执行第二部分:(task = getTask()) != null) (注:getTask()方法稍后再讲,它返回一个用户已提交的任务)。假设task得到一个已提交的任务,从而16行执行的是新获得的新任务的run()方法。后面通case1,最后task也会被置为null,以后循环都进入case2。
getTask()
case2中每次循环都要获取一个新的已提交的任务,那任务从哪来呢?简化如下
public class ThreadPoolExecutor extends AbstractExecutorService { ... private Runnable getTask() { boolean timedOut = false; // Did the last poll() time out? for (;;) { try { Runnable r = timed ? workQueue.poll(keepAliveTime, TimeUnit.NANOSECONDS) : workQueue.take(); if (r != null) return r; timedOut = true; } catch (InterruptedException retry) { timedOut = false; } } } ...}
我们先看简单的7-11行。
首先,workQueue是一个线程安全的BlockingQueue,大部分时候使用的实现类是LinkedBlockingQueue。
private final BlockingQueue<Runnable> workQueue;
假设timed为 false,则调用阻塞的take()方法,返回的一定不是null,从而在11行return退出,将获取到的task交给某个worker线程去执行。
workQueue中的元素从哪来呢 ?这就要回到最初的execute()方法了。
execute()
public class ThreadPoolExecutor extends AbstractExecutorService { ... 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); } ...}
前面以8行的参数为例,此时,线程池中的线程数未达到最小线程池的大小corePoolSize,通常这个时候直接在第10行返回。
如果不满足8行的条件,则会执行到13行的。isRunning(c)判断线程池是否未关闭,这里我们只关注未关闭的情况;接下来就会执行布尔表达式的第二部分workQueue.offer(command),尝试将任务放入到队列workQueue中。
workQueue.offer()的行为取决于线程池持有的BlockingQueue实例。
Executors.newFixedThreadPool()、Executors.newSingleThreadExecutor()创建的线程池使用LinkedBlockingQueue,而Executors.newCachedThreadPool()创建的线程池则使用SynchronousQueue。
public class Executors { ... public static ExecutorService newFixedThreadPool(int nThreads) { return new ThreadPoolExecutor(nThreads, nThreads, 0L, TimeUnit.MILLISECONDS, new LinkedBlockingQueue<Runnable>()); } public static ExecutorService newSingleThreadExecutor() { return new FinalizableDelegatedExecutorService (new ThreadPoolExecutor(1, 1, 0L, TimeUnit.MILLISECONDS, new LinkedBlockingQueue<Runnable>())); } public static ExecutorService newCachedThreadPool() { return new ThreadPoolExecutor(0, Integer.MAX_VALUE, 60L, TimeUnit.SECONDS, new SynchronousQueue<Runnable>()); }}
以LinkedBlockingQueue为例,创建时不配置容量,即创建为无界队列,则LinkedBlockingQueue#offer()永远返回true,从而进入execute()13-19行。
当workQueue.offer()返回true时,已经将任务command放入队列workQueue中。当未来某个时刻,某worker完成一个任务后,会通过getTask()从workerQueue在获取一个任务继续执行,直到线程池关闭,直到海枯石烂。
case2小结
可以看到,实际上,线程池的核心原理与对象池模型无关,而是生产者-消费者模型。
- 生产者(调用submit()或者execute()方法)将任务Task放入队列
- 消费者(worker线程)循环从任务队列中取出任务处理(执行task.run())
钩子方法
回到runWorker()方法,在执行任务的过程中,线程池保留了一些钩子方法,如beforeExecute()、afterExecute()。用户可以在实现自己的线程池时,可以通过覆写钩子方法为线程池添加功能。
总结
对象池和线程池关系不大,但是经常会混淆。线程池的实现很有意思。在追溯源码之前,我一直以为线程池就是把线程存起来,用的时候取出一个执行任务;看了源码才知道它实现如此之妙,简洁优雅效率高。源码才是最好的老师。
- 对象池和线程池
- Java多线程 锁对象和线程池
- 线程和线程对象
- 线程和线程池
- 线程和线程池
- 线程和线程池
- 线程和线程池
- Java 数据库连接池、线程池和对象池总结
- Java 数据库连接池、线程池和对象池总结
- 对象池-线程池基于对象池
- 线程方法和对象
- java,同一个Runnable对象扔线程池多次和多个实例对象扔线程池有啥不同啊?
- 线程池之同步对象
- android 线程池对象-ThreadPoolExecutor
- android 线程池对象-ThreadPoolExecutor
- android 线程池对象-ThreadPoolExecutor
- android 线程池对象-ThreadPoolExecutor
- 非线程安全对象池
- 51nod 1536 不一样的猜数游戏 (找规律+素数筛)
- 6.3
- Ubuntu安装caffe和rcnn的兼容性问题
- idea破解
- Java的io类的使用场景
- 对象池和线程池
- 1027. 打印沙漏(20)
- STM32多串口共用printf打印串口数据
- HDOJ 1395 2^x mod n = 1
- 测试Java的静态代码快执行时机
- Leetcode:Triangle
- 基于增量的矩阵聚类
- hdu 5903
- 【Mybatis学习】Mybatis框架中的动态sql