前言

为什么要从对象池开始呢,先从一个网络IO操作的demo说起

比如下面这段代码,显而易见已经在代码中使用了一个固定大小的线程池,所以现在的重点在实现Runnble接口的匿名对象上,这个对象每次创建线程都要重复创建。

假设有个提供http服务的程序,每天要处理几万,上百万等等的用户请求,那么我们的程序岂不是每次请求都会创建一次Runnble的实现对象?这种大量重复创建同一个对象显然会造成大量的内存资源浪费(如果再增加GC的要回收他们所需要消耗的系统资源,显然耗费的资源只会比表面看起来更多)

  1. /**
  2. * 一个请求
  3. * @param host -主机地址
  4. * @param port -端口号
  5. * @param size -线程数
  6. * @return
  7. */
  8. public static List<Socket> request(String host, int port, int size) {
  9. ScheduledThreadPoolExecutor exe = new ScheduledThreadPoolExecutor(size);
  10. List<Socket> list = new ArrayList<Socket>(size);
  11. int errsize = 0;
  12. for (int i = 0; i < size; i++) {
  13. System.err.println("正在创建连接线程" + i);
  14. Socket socket;
  15. try {
  16. socket = request(host, port);
  17. exe.execute(new Runnable() {
  18. @Override
  19. public void run() {
  20. handler(socket);
  21. }
  22. });
  23. list.add(socket);
  24. } catch (IOException e) {
  25. errsize++;
  26. }
  27. }
  28. System.err.println("请求失败次数" + errsize);
  29. return list;
  30. }
  31. public static void handler(Socket socket) {
  32. try {
  33. PrintWriter writer = new PrintWriter(
  34. new OutputStreamWriter(new BufferedOutputStream(socket.getOutputStream(), 8192), "utf-8"), true);
  35. for (int i = 5; i > 0; i--) {
  36. writer.print("xxx");
  37. writer.write("sss");
  38. writer.println("123");
  39. }
  40. Thread.sleep(1000 * 60);
  41. socket.close();
  42. } catch (Exception e) {
  43. // TODO Auto-generated catch block
  44. e.printStackTrace();
  45. }
  46. }

除了Runnable之外,在实际实践中还有很多类似的重复创建对象的示例,不再一一举例。

既然知道了重复创建对象是个很耗费资源的一件事情,那么我们如何优化呢?

没错,利用“对象池”来重复利用有限资源的对象(比如数据库连接是有上限的)

线程池、数据库连接池这些都是我们平时司空见惯的“池”,那么我们的主题就是实现一个简单的“对象池”

对象池实现

对象池调用接口

先定义一个对象池接口,这个接口抽象了几个调用方法,一般只用得到这三个接口

  1. package cc.eguid.test.SimulationServer.pool;
  2. public interface BasePool<T> {
  3. /**
  4. * 从对象池中获取空闲对象(如果对象池已满会一直阻塞,直到有空闲对象)
  5. * @return
  6. */
  7. public T getObject();
  8. /**
  9. * 回收对象到对象池
  10. * @param obj
  11. */
  12. public void returnObject(T obj);
  13. /**
  14. * 是否有空闲对象
  15. * @return
  16. */
  17. public boolean has();
  18. }

对象工厂

想象一下,如果我们直接实现该接口之后,我们要如何创建一个未知的对象?比如创建带参数的对象或者其他方式创建对象,为了应对这种情况,我们需要抽象出一个对象创建工厂接口,另外提供一个默认的简单的对象工厂。

  1. package cc.eguid.test.SimulationServer.pool;
  2. public interface CreateObjectFactory<T> {
  3. /**
  4. * 创建对象
  5. * @return
  6. */
  7. public T create(Class<T> objectClass) ;
  8. /**
  9. * 销毁对象
  10. */
  11. public void detory(T obj);
  12. /**
  13. * 重置对象
  14. */
  15. public T reset(T obj);
  16. }
  1. package cc.eguid.test.SimulationServer.pool;
  2. public class DefaultObjectFactory<T> implements CreateObjectFactory<T> {
  3. @Override
  4. public T create(Class<T> objectClass) {
  5. try {
  6. return objectClass.newInstance();
  7. } catch (InstantiationException | IllegalAccessException e) {
  8. // TODO Auto-generated catch block
  9. e.printStackTrace();
  10. }
  11. return null;
  12. }
  13. @Override
  14. public void detory(T obj) {
  15. obj = null;
  16. }
  17. @Override
  18. public T reset(T obj) {
  19. T newobj = create((Class<T>) obj.getClass());
  20. detory(obj);
  21. return newobj;
  22. }
  23. }

对象池的管理

现在,对象创建有了,我们要实现对象池的接口,来对对象的创建、重置、缓存、获取和回收操作进行管理了。

  1. package cc.eguid.test.SimulationServer.pool;
  2. import java.util.concurrent.ConcurrentLinkedQueue;
  3. import org.apache.log4j.Logger;
  4. /**
  5. * 对象池管理
  6. *
  7. * @author eguid
  8. * @date 2018-01-09
  9. * @param <T>
  10. */
  11. public class BaseObjectPool<T> implements BasePool<T> {
  12. final static Logger log = Logger.getLogger(BaseObjectPool.class);
  13. /**
  14. * 空闲对象池(已交回的对象),初始化时都是空闲的对象
  15. */
  16. ConcurrentLinkedQueue<T> idleObjects = null;
  17. volatile int usingAmount = 0;// 当前已使用的对象数量(出借的对象)
  18. volatile int activeSize = 0;// 创建的对象数量(当对象池满时,该值等于对象池大小)
  19. volatile boolean isHaveIdle = Boolean.TRUE;
  20. /**
  21. * 对象池中对象的总数量(通过初始化方法确定,不可修改)
  22. */
  23. private final int size;
  24. private Class<T> objectClass = null;// 要创建的对象class
  25. /**
  26. * 创建对象的工厂
  27. */
  28. private CreateObjectFactory<T> factory;
  29. /**
  30. * 默认
  31. *
  32. * @param factory -可以为空,为空则根据默认对象工厂类创建对象
  33. * @param size
  34. * @param cl
  35. */
  36. public BaseObjectPool(int size, Class<T> cl) {
  37. this(null, size, cl);
  38. }
  39. /**
  40. * 默认
  41. *
  42. * @param factory -可以为空,为空则根据默认对象工厂类创建对象
  43. * @param size
  44. * @param cl
  45. */
  46. public BaseObjectPool(CreateObjectFactory<T> factory, int size, Class<T> cl) {
  47. this.size = size;
  48. this.objectClass = cl;
  49. if (factory == null) {
  50. factory = new DefaultObjectFactory<T>();
  51. }
  52. this.factory = factory;
  53. idleObjects = new ConcurrentLinkedQueue<>();
  54. }
  55. @Override
  56. public synchronized T getObject() {
  57. T obj = null;
  58. if (isFull()) {// 对象池是否已满,如果满了就从空闲连接池中获取空闲对象
  59. if (has()) {// 如果没有空闲对象,那么将会一直阻塞
  60. if ((obj = idleObjects.poll()) != null) {// 如果空闲对象池中也没有空闲对象,那么将返回null
  61. log.info("从对象池中取出空闲的对象");
  62. usingAmount++;// 使用对象增加
  63. }
  64. }
  65. } else {
  66. log.info("创建新的对象到对象池");
  67. obj = factory.create(this.objectClass);
  68. activeSize++;// 活动对象增加
  69. usingAmount++;// 使用对象增加
  70. }
  71. return obj;
  72. }
  73. @Override
  74. public synchronized void returnObject(T obj) {
  75. log.info("当前回收的对象:" + obj.getClass().getName());
  76. if (obj != null) {
  77. log.info("准备回收对象到对象池");
  78. T idleobj = factory.reset(obj);
  79. if (idleObjects.offer(idleobj)) {// 将对象放入空闲对象
  80. usingAmount--;
  81. log.info("回收对象后当前对象池状态:(对象创建数量:" + getActiveSize() + ",对象当前使用数量:" + getUsingAmount() + ")");
  82. } else {
  83. log.warn("回收失败");
  84. }
  85. }
  86. }
  87. /**
  88. * 空闲对象池中是否还有空闲对象可以获取
  89. */
  90. public boolean has() {
  91. return usingAmount < size;
  92. }
  93. /**
  94. * 池中对象数量是否已经达到最大值(对象池是否已满)
  95. *
  96. * @return
  97. */
  98. public boolean isFull() {
  99. return activeSize >= size;
  100. }
  101. public CreateObjectFactory<T> getFactory() {
  102. return factory;
  103. }
  104. public void setFactory(CreateObjectFactory<T> factory) {
  105. this.factory = factory;
  106. }
  107. public ConcurrentLinkedQueue<T> getIdleObjects() {
  108. return idleObjects;
  109. }
  110. /**
  111. * 获取当前正在使用的对象数量
  112. *
  113. * @return
  114. */
  115. public int getUsingAmount() {
  116. return usingAmount;
  117. }
  118. /**
  119. * 获取当前创建的对象数量
  120. *
  121. * @return
  122. */
  123. public int getActiveSize() {
  124. return activeSize;
  125. }
  126. public int getSize() {
  127. return size;
  128. }
  129. public Class<T> getObjectClass() {
  130. return objectClass;
  131. }
  132. }

我们利用了一个线程安全的FIFO队列来实现对象的缓存,利用使用数量和创建数量两个实时变量来保证多线程下的可读性和对象的获取回收操作前的必要判断。

使用一个不算太复杂的例子来测试一下对象池是否好用

  1. package cc.eguid.test.SimulationServer;
  2. import java.io.IOException;
  3. import java.net.InetSocketAddress;
  4. import java.net.ServerSocket;
  5. import java.net.Socket;
  6. import java.net.SocketAddress;
  7. import java.net.SocketTimeoutException;
  8. import cc.eguid.test.SimulationServer.handler.CommonHandler;
  9. import cc.eguid.test.SimulationServer.log.CommonParent;
  10. import cc.eguid.test.SimulationServer.service.ServiceManager;
  11. /**
  12. * 套接字服务
  13. *
  14. * @author eguid
  15. * @date 2018-01-08
  16. */
  17. public class ServerService extends CommonParent {
  18. private static final long serialVersionUID = 1L;
  19. int port;
  20. int backlog = 1000;// 连接等待队列
  21. int size = 1000;// 任务线程总数(默认线程数1000)
  22. ServerSocket server = null;
  23. ServiceManager manager = null;
  24. public ServerService(int port) {
  25. initServerSocket(port, null, null);
  26. }
  27. public ServerService(int port, int size) {
  28. initServerSocket(port, size, null);
  29. }
  30. public ServerService(int port, int size, int backlog) {
  31. initServerSocket(port, size, backlog);
  32. }
  33. /**
  34. * 初始化服务(立即失败模式,如果启动任意服务初始化失败,则立刻抛出异常终止服务)
  35. *
  36. * @param port
  37. * @param size
  38. * @param backlog
  39. * @return
  40. */
  41. private ServerSocket initServerSocket(int port, Integer size, Integer backlog) {
  42. this.port = port;
  43. this.size = size;
  44. try {
  45. server = new ServerSocket();
  46. // 该方法必须在套接字创建之前确定,否则无效
  47. // 性能首选项(短连接优先级最低,低延迟优先级最高,高带宽为次重要)
  48. server.setPerformancePreferences(0, 2, 1);
  49. SocketAddress sa = new InetSocketAddress(port);
  50. server.bind(sa, backlog);
  51. manager = new ServiceManager(size+backlog);
  52. return server;
  53. } catch (Exception e) {
  54. // 如果端口被占用应该立即失败
  55. log.error("服务启动失败", e);
  56. e.printStackTrace();
  57. }
  58. return server;
  59. }
  60. public ServerSocket getServer() {
  61. return server;
  62. }
  63. /**
  64. * 开始监听处理连接请求
  65. *
  66. * @param handler
  67. */
  68. public void start(CommonHandler handler) {
  69. if (server != null && !server.isClosed()) {
  70. Socket soc = null;
  71. for (;;) {
  72. try {
  73. soc = waitConnect();
  74. if(soc!=null){
  75. manager.execute(soc, handler);
  76. }
  77. } catch (Exception e) {// 屏蔽异常,防止单个连接异常影响主程序稳定运行
  78. // 单个线程的连接失败不应该影响整体
  79. log.warn("等待新的连接失败", e);
  80. }
  81. }
  82. }
  83. }
  84. /**
  85. * 等待连接(立即失败模式,除了超时异常以外,其他异常会立即抛出)
  86. *
  87. * @return
  88. * @throws IOException
  89. */
  90. private Socket waitConnect() throws IOException {
  91. Socket soc = null;
  92. try {
  93. if ((soc = server.accept()) != null) {
  94. if (soc != null && soc.isConnected()) {
  95. log.info("建立了一个连接");
  96. return soc;
  97. }
  98. }
  99. } catch (SocketTimeoutException se) {
  100. log.warn("套接字连接等待超时", se);
  101. // 连接等待超时,继续等待
  102. return null;
  103. } catch (IOException e) {// 屏蔽单个连接错误
  104. log.error("创建一个新连接请求失败", e);
  105. return null;
  106. }
  107. return soc;
  108. }
  109. }
  1. package cc.eguid.test.SimulationServer.service;
  2. import java.io.IOException;
  3. import java.net.Socket;
  4. import java.util.concurrent.ScheduledThreadPoolExecutor;
  5. import java.util.concurrent.ThreadPoolExecutor;
  6. import cc.eguid.test.SimulationServer.handler.CommonHandler;
  7. import cc.eguid.test.SimulationServer.handler.Tasker;
  8. import cc.eguid.test.SimulationServer.log.CommonParent;
  9. import cc.eguid.test.SimulationServer.pool.BaseObjectPool;
  10. import cc.eguid.test.SimulationServer.pool.CreateObjectFactory;
  11. /**
  12. * 任务管理器
  13. *
  14. * @author eguid
  15. * @date 2018-01-08
  16. */
  17. public class ServiceManager extends CommonParent {
  18. private static final long serialVersionUID = 1L;
  19. private ThreadPoolExecutor excutor = null;
  20. public static BaseObjectPool<Tasker> pool = null;
  21. public static void returnObj(Tasker obj) {
  22. pool.returnObject(obj);
  23. }
  24. public ServiceManager(int size) {
  25. excutor = new ScheduledThreadPoolExecutor(size);
  26. //重新实现对象创建、销毁和重置方式
  27. pool = new BaseObjectPool<Tasker>(new CreateObjectFactory<Tasker>() {
  28. @Override
  29. public Tasker create(Class<Tasker> objectClass) {
  30. return new Tasker();
  31. }
  32. @Override
  33. public void detory(Tasker obj) {
  34. // 先销毁内部的连接
  35. Socket soc = obj.getSocket();
  36. if (soc != null && !soc.isClosed()) {
  37. try {
  38. soc.close();
  39. } catch (IOException e) {
  40. soc = null;
  41. }
  42. }
  43. obj = null;
  44. }
  45. @Override
  46. public Tasker reset(Tasker obj) {
  47. Socket soc = obj.getSocket();
  48. if (soc != null && !soc.isClosed()) {
  49. try {
  50. soc.close();
  51. } catch (IOException e) {
  52. soc = null;
  53. }
  54. }
  55. soc = null;
  56. obj.setSocket(null);// 重置只需要销毁socket即可
  57. obj.setHandler(null);
  58. return obj;
  59. }
  60. }, size, Tasker.class);
  61. }
  62. public void execute(Socket socket, CommonHandler handler) {
  63. Tasker task = pool.getObject();
  64. if (task == null) {
  65. log.warn("没有空闲对象");
  66. return;
  67. }
  68. task.setHandler(handler);
  69. task.setSocket(socket);
  70. excutor.execute(task);
  71. }
  72. }
  1. package cc.eguid.test.SimulationServer.handler;
  2. import java.io.IOException;
  3. import java.net.Socket;
  4. import cc.eguid.test.SimulationServer.log.CommonParent;
  5. import cc.eguid.test.SimulationServer.service.ServiceManager;
  6. //重新实现了Runnable接口,方便处理网络流
  7. public class Tasker extends CommonParent implements Runnable {
  8. private static final long serialVersionUID = 1L;
  9. Socket socket = null;
  10. CommonHandler handler = null;
  11. public Tasker() {
  12. }
  13. private Tasker(Socket socket, CommonHandler handler) {
  14. this.socket = socket;
  15. this.handler = handler;
  16. }
  17. public Socket getSocket() {
  18. return socket;
  19. }
  20. public void setSocket(Socket socket) {
  21. this.socket = socket;
  22. }
  23. public CommonHandler getHandler() {
  24. return handler;
  25. }
  26. public void setHandler(CommonHandler handler) {
  27. this.handler = handler;
  28. }
  29. @Override
  30. public void run() {
  31. try {
  32. handler.handler(socket);
  33. } catch (Exception e) {// 允许通过抛出异常方式退出线程
  34. log.warn("处理异常结束,正在试图正常关闭连接", e);
  35. if (socket != null && !socket.isClosed()) {
  36. try {
  37. socket.close();
  38. } catch (IOException e1) {
  39. socket = null;
  40. log.warn("线程意外结束,尝试关闭连接失败", e1);
  41. }
  42. }
  43. } finally {
  44. ServiceManager.returnObj(this);// 回收对象到对象池
  45. }
  46. }
  47. }
  1. package cc.eguid.test.SimulationServer;
  2. import java.io.BufferedReader;
  3. import java.io.InputStreamReader;
  4. import java.net.Socket;
  5. import java.nio.charset.Charset;
  6. import org.slf4j.Logger;
  7. import org.slf4j.LoggerFactory;
  8. import cc.eguid.test.SimulationServer.handler.CommonHandler;
  9. /**
  10. * 启动
  11. * @author eguid
  12. * @date 2018-01-09
  13. */
  14. public class App {
  15. final static Logger log = LoggerFactory.getLogger(App.class);
  16. public static void main(String[] args) {
  17. System.out.println("Hello World!");
  18. // 监听8181端口,最多同时10个任务线程,连接等待队列最多允许20个连接同时等待
  19. ServerService server = new ServerService(8181, 10, 20);
  20. //处理器只创建一次
  21. server.start(new CommonHandler() {
  22. @Override
  23. public void handler(Socket socket) throws Exception {
  24. BufferedReader reader = null;
  25. try {
  26. reader = new BufferedReader(
  27. new InputStreamReader(socket.getInputStream(), Charset.forName("utf-8")));
  28. String msg = null;
  29. for (;;) {
  30. if (null != (msg = reader.readLine())) {
  31. log.info(Thread.currentThread().getName()+":"+msg);
  32. }
  33. Thread.yield();
  34. Thread.sleep(50);
  35. }
  36. } catch (Exception e) {// 获取连接失败,直接停止
  37. log.error("无法获取连接,连接可能已经终止,正在推出线程", e);
  38. // TODO Auto-generated catch block
  39. throw e;
  40. }
  41. }
  42. });
  43. }
  44. }

除了本篇文章实现的简单的对象池外,平时开发中常见的对象池有Apache-commons-pool和2,Apache-commons-pool常用于DBCP,c3p0,druid等等常见的数据库连接池中。

Java网络与多线程系列之1:实现一个简单的对象池的更多相关文章

  1. 用Java实现一个通用并发对象池

    这篇文章里我们主要讨论下如何在Java里实现一个对象池.最近几年,Java虚拟机的性能在各方面都得到了极大的提升,因此对大多数对象而言,已经没有必要通过对象池来提高性能了.根本的原因是,创建一个新的对 ...

  2. Tomcat详解系列(1) - 如何设计一个简单的web容器

    Tomcat - 如何设计一个简单的web容器 在学习Tomcat前,很多人先入为主的对它的认知是巨复杂的:所以第一步,在学习它之前,要打破这种观念,我们通过学习如何设计一个最基本的web容器来看它需 ...

  3. 自己动手系列----使用数组实现一个简单的Map

    数组对于每一门编程语言来说都是重要的数据结构之一,当然不同语言对数组的实现及处理也不尽相同.Java 语言中提供的数组是用来存储固定大小的同类型元素. 这里提一下,数组的优缺点: 优点: 1. 使用索 ...

  4. JBoss 系列七十:一个简单的 CDI Web 应用

    概述 本文通过一个简单的 CDI Web 应用演示dependency injection, scope, qualifiers 以及EL整合.应用部署完成后我们可以通过http://localhos ...

  5. Java入门篇(一)——如何编写一个简单的Java程序

    最近准备花费很长一段时间写一些关于Java的从入门到进阶再到项目开发的教程,希望对初学Java的朋友们有所帮助,更快的融入Java的学习之中. 主要内容包括JavaSE.JavaEE的基础知识以及如何 ...

  6. linux网络编程-一个简单的线程池(41)

    有时我们会需要大量线程来处理一些相互独立的任务,为了避免频繁的申请释放线程所带来的开销,我们可以使用线程池 1.线程池拥有若干个线程,是线程的集合,线程池中的线程数目有严格的要求,用于执行大量的相对短 ...

  7. java网络编程——多线程数据收发并行

    基本介绍与思路 收发并行 前一篇博客中,完成了客户端与服务端的简单TCP交互,但这种交互是触发式的:客户端发送一条消息,服务端收到后再回送一条.没有做到收发并行.收发并行的字面意思很容易理解,即数据的 ...

  8. 多线程系列之自己实现一个 lock 锁

    我们面试中经常会被问到多线程相关知识,这一块内容往浅了说大家都会,但是一问到底层实现原理,我们往往就一脸懵逼. 这段时间准备好好学习多线程,接下来会写一系列关于多线程的知识. 我们首先要了解线程,百度 ...

  9. java中实现多线程的几种方式(简单实现)

    一.以下只是简单的实现多线程 1:继承Thread 2:实现 Runnable 3:实现callable 如果需要返回值使用callable,如果不需要返回最好使用runnable,因为继承只能单继承 ...

随机推荐

  1. android开发系列之视频断点续传

    今天在这篇博客里面,我想说说自己在这几天遇到的一个棘手的问题,就是视频断点续传的问题.其实这在我们开发中是一个很常见的应用场景,比如视频.音频.pdf等相关的文档.如果之前没有接触过的话,你也许会被这 ...

  2. VESA-ADV7123-SOCKIT-DE2115

    /*--VGA Timing--Horizontal :-- ______________ _____________-- | | |--_______________| VIDEO |_______ ...

  3. eclipse-jee版配置tomcat

    Eclipse作为一款优秀的java开发开源IDE,集成了许多优秀的开发控件.下来我就如何安装eclipse及插件进行说明: 一.JDK安装   JDK是作为整个java的核心,包括运行环境,编译工具 ...

  4. uboot之bootm以及go命令的实现

    本文档简单介绍了uboot中用于引导内核的命令bootm的实现,同时分析了uImage文件的格式,还简单看了一下uboot下go命令的实现 作者: 彭东林 邮箱: pengdonglin137@163 ...

  5. 小tip: DOM appendHTML实现及insertAdjacentHTML

    一.无人不识君 据说今天是邓丽君奶奶会见马克思的日子,所谓“无人不识君”就多了份“无人不识邓丽君”之意. JS中有很多基本DOM方法,例如createElement, parentNode等,其中,a ...

  6. 03 redis之string类型命令解析

    Redis字符串类型的操作 set key value [ex 秒数] / [px 毫秒数] [nx] /[xx] 如: set a 1 ex 10 , 10秒有效 Set a 1 px 9000 , ...

  7. CentOS Python 安装MySQL-python

    一.安装mysql yum list | grep mysql >>yum install -y mysql-server mysql mysql-devel CentOS 7的yum源中 ...

  8. CSDN专訪:大数据时代下的商业存储

    原文地址:http://www.csdn.net/article/2014-06-03/2820044-cloud-emc-hadoop 摘要:EMC公司作为全球信息存储及管理产品方面的率先公司,不久 ...

  9. Android开发:LocationManager获取经纬度及定位过程(附demo)

    在Android开发其中.常常须要用到定位功能,尤其是依赖于地理位置功能的应用.非常多人喜欢使用百度地图,高德地图提供的sdk.开放API,可是在只须要经纬度,或者城市,街道地址等信息.并不须要提供预 ...

  10. 前后端分离之fiddler前端开发代理 autoresponder 正则表达式 regex:(?insx) 修正符详解

    regex:(?isx)^http://127.0.0.1:3000(/dlscene)?/order/(\w*) http://127.0.0.1:8080/dlscene/order/$2 上面这 ...