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

前言

为什么要从对象池开始呢,先从一个网络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.   
  32.     public static void handler(Socket socket) {  
  33.     try {  
  34.         PrintWriter writer = new PrintWriter(  
  35.             new OutputStreamWriter(new BufferedOutputStream(socket.getOutputStream(), 8192), "utf-8"), true);  
  36.         for (int i = 5; i > 0; i--) {  
  37.         writer.print("xxx");  
  38.         writer.write("sss");  
  39.         writer.println("123");  
  40.         }  
  41.         Thread.sleep(1000 * 60);  
  42.         socket.close();  
  43.     } catch (Exception e) {  
  44.         // TODO Auto-generated catch block  
  45.         e.printStackTrace();  
  46.     }   
  47.     }  

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

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

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

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

对象池实现

对象池调用接口

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

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

对象工厂

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

  1. package cc.eguid.test.SimulationServer.pool;  
  2.   
  3. public interface CreateObjectFactory<T> {  
  4.     /** 
  5.      * 创建对象 
  6.      * @return 
  7.      */  
  8.     public T create(Class<T> objectClass) ;  
  9.       
  10.     /** 
  11.      * 销毁对象 
  12.      */  
  13.     public void detory(T obj);  
  14.       
  15.     /** 
  16.      * 重置对象 
  17.      */  
  18.     public T reset(T obj);  
  19. }  

 

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

对象池的管理

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

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

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

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

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

 

  1. package cc.eguid.test.SimulationServer.handler;  
  2.   
  3. import java.io.IOException;  
  4. import java.net.Socket;  
  5. import cc.eguid.test.SimulationServer.log.CommonParent;  
  6. import cc.eguid.test.SimulationServer.service.ServiceManager;  
  7. //重新实现了Runnable接口,方便处理网络流  
  8. public class Tasker extends CommonParent implements Runnable {  
  9.     private static final long serialVersionUID = 1L;  
  10.   
  11.     Socket socket = null;  
  12.     CommonHandler handler = null;  
  13.   
  14.     public Tasker() {  
  15.     }  
  16.   
  17.     private Tasker(Socket socket, CommonHandler handler) {  
  18.     this.socket = socket;  
  19.     this.handler = handler;  
  20.     }  
  21.   
  22.     public Socket getSocket() {  
  23.     return socket;  
  24.     }  
  25.   
  26.     public void setSocket(Socket socket) {  
  27.     this.socket = socket;  
  28.     }  
  29.   
  30.     public CommonHandler getHandler() {  
  31.     return handler;  
  32.     }  
  33.   
  34.     public void setHandler(CommonHandler handler) {  
  35.     this.handler = handler;  
  36.     }  
  37.   
  38.     @Override  
  39.     public void run() {  
  40.     try {  
  41.         handler.handler(socket);  
  42.     } catch (Exception e) {// 允许通过抛出异常方式退出线程  
  43.         log.warn("处理异常结束,正在试图正常关闭连接", e);  
  44.         if (socket != null && !socket.isClosed()) {  
  45.         try {  
  46.             socket.close();  
  47.         } catch (IOException e1) {  
  48.             socket = null;  
  49.             log.warn("线程意外结束,尝试关闭连接失败", e1);  
  50.         }  
  51.         }  
  52.     } finally {  
  53.         ServiceManager.returnObj(this);// 回收对象到对象池  
  54.     }  
  55.     }  
  56. }  
  1. package cc.eguid.test.SimulationServer;  
  2.   
  3. import java.io.BufferedReader;  
  4. import java.io.InputStreamReader;  
  5. import java.net.Socket;  
  6. import java.nio.charset.Charset;  
  7.   
  8. import org.slf4j.Logger;  
  9. import org.slf4j.LoggerFactory;  
  10.   
  11. import cc.eguid.test.SimulationServer.handler.CommonHandler;  
  12. /** 
  13.  * 启动 
  14.  * @author eguid 
  15.  * @date 2018-01-09 
  16.  */  
  17. public class App {  
  18.     final static Logger log = LoggerFactory.getLogger(App.class);  
  19.   
  20.     public static void main(String[] args) {  
  21.     System.out.println("Hello World!");  
  22.     // 监听8181端口,最多同时10个任务线程,连接等待队列最多允许20个连接同时等待  
  23.     ServerService server = new ServerService(8181, 10, 20);  
  24.     //处理器只创建一次  
  25.     server.start(new CommonHandler() {  
  26.         @Override  
  27.         public void handler(Socket socket) throws Exception {  
  28.         BufferedReader reader = null;  
  29.         try {  
  30.             reader = new BufferedReader(  
  31.                 new InputStreamReader(socket.getInputStream(), Charset.forName("utf-8")));  
  32.             String msg = null;  
  33.             for (;;) {  
  34.             if (null != (msg = reader.readLine())) {  
  35.                 log.info(Thread.currentThread().getName()+":"+msg);  
  36.             }  
  37.              Thread.yield();  
  38.              Thread.sleep(50);  
  39.             }  
  40.         } catch (Exception e) {// 获取连接失败,直接停止  
  41.             log.error("无法获取连接,连接可能已经终止,正在推出线程", e);  
  42.             // TODO Auto-generated catch block  
  43.             throw e;  
  44.         }  
  45.         }  
  46.     });  
  47.     }  
  48. }  


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

posted @ 2018-01-09 19:33  eguid  阅读(448)  评论(0编辑  收藏  举报