1. 前言
当我们的应用中创建一个十分最重量级的对象的时候,往往为了节省资源成本,使用单例模式,整个的应用中就只有一个对象供大家使用。这样是节省了不少资源,也是大多数应用的做法。不过如果遇到并发量十分大的情况下,并发访问的问题也就出现了。OK即使您不用加锁synchronized,利用ThreadLoacl进行安全变量的副本,但是维持变量副本的资源也是需要消耗资源的。而且对于一个重量级的对象的多个方法多个线程同时调用此对象的同一个局部变量,多个副本的维护实际上也是挺占用资源的。
2. 轻量级对象
当然不是说所有的对象都适合池化的。池化的对象是一个重量级对象,什么是重量级对象。回答这个问题先说说什么是轻量级对象。比如我们做Web系统的DAO(注意:这里绝对不包括EJB的EAO),就是为了调用持久层框架完成增、删、改、查原子性操作。实际就算是轻量级的对象,DAO没必要含有太复杂的其他对象,如果需要其他辅助对象为其服务:比如事务插入、日志操作记录。使用AOP面向他的切面进行拦截即可。Spring有整套方案,不做扩展。创建这些对象,在启动Web应用的时候实例化放到容器中即可,想要,容器自动为您注入。容器保证了对象的线程安全,所以这些对象是轻量级对象。
3. 重量级对象
那什么又是重量级对象呢,咱们用一个谁都能想到的例子,数据库连接java.sql.Connection。为何说Connection是一个重量级对象呢?我们在JDBC编程的时候仅仅使用了很少量的代码即完成了数据库的连接操作。一个Connection对象为我们操作数据库做了什么?
1):通过JDBC的驱动Driver屏蔽底层数据库网络协议,让我们不必关心具体的数据库通讯协议(Mysql有Mysql的协议、Oracle有Oracle的通讯协议)
2):通过数据库网络协议打开网络socket连接管道
3):根据建立的网络通道发送握手指令(所谓握手指令就是包含数据库用户名、数据库密码的二进制信息)
4):接收数据库端的握手结果信息再决定下面的通讯是否进行
关键是建立网络socket通道耗费资源着实不小。而且java.sql.Connection需要一直维持着这个网络通道。
相比于上面提到的DAO对象的创建,这个Connection对象创建的时候是不是确实显得历经沧桑。咱们举个例子,就像唐僧取经一样,李世民想要经书超度为他建功立业的亡魂,唐三藏就去了,历经千辛万苦取回来了,李世民让其诵念经文超度亡魂后发现这段时间那些死去的冤魂没再缠着他了,取回来的经文这个时候被李世民看经书占地方,下令烧掉了(垃圾回收器回收了)。过了一段时间又有亡魂开始缠着他了!怎么办?李世民说:“哦,御弟,麻烦你再去见佛祖一趟吧!”唐三藏之后又花了12年,再去西天见佛祖,取回了真经超度亡魂~~如果当时唐三藏说:“阿妹喂,陛下,当时您要是不下令烧掉,将经书交给我的大徒弟去管理,就不必这么麻烦~等我取回经书了,您也就快那什么啦”。
4. 对象池
对象池的概念和现实生活中的图书馆的管理极其相似。阅读这项看一本书,用读书卡去借一本书、看完了还回来,如果图书馆的书不够用了怎么办?那就麻烦图书采购小刘去市场买几本回来吧。这个本书太受欢迎了,原始的书的数量不够用啊。对象池就像是一个图书馆,图书管理员就像是对象调度器,借书人就像是调用对象的客户端。需要维护的就是图书馆和借书的记录凭证。这样看来资源需要维护图书馆和借书系统。但是这种维护在某种程度上来说是值得的。回到我们的编程世界中,Apache的common-pool组件就是一个开源的对象池工具,我们可以借助它的接口,就可以将重量级的对象做池化。
5. 环境搭建
首先从apache站点下载commons-pool组件包。将lib下面的jar包拷贝到项目classpath中。因为此实话组件还依赖于其他的apache的commos组件——commons-collections。所以还需要将此jar包加入到classpath。笔者搭建好的环境如下
Java代码
- /ApacheCommonPool/lib/commons-collections-2.1.1.jar
- /ApacheCommonPool/lib/commons-pool-1.5.6-javadoc.jar
- /ApacheCommonPool/lib/commons-pool-1.5.6-sources.jar
- /ApacheCommonPool/lib/commons-pool-1.5.6.jar
简单的池化环境已经搭建完毕
6. 使用例程
在此历程中,我们测试一下Java常用对像,也是最通用的轻量级对象String的池化。再测试一下自定义的实体对象的池化。再次说明一点:String是绝对不适合做池化的,首先java.lang.String对象本身就不是什么重量级对象,初始化String对象也绝对不用经过什么西天取经的过程才能产生出来的,并且JVM已经对String做了池化处理。有一个String池维护已经创建好的字符串引用对象值。在此使用String仅仅是表示如何使用池化操作罢了。实际应用中,千万不要池化String对象。否则就有点得不偿失了。首先给出简单的POJO实体对象代码。
Java代码
- package key;
-
- public class Person {
-
- String id;
- String name;
-
- public Person() {
-
- }
-
- public Person(String id, String name) {
- this.id = id;
- this.name = name;
- }
-
- public String getId() {
- return id;
- }
-
- public void setId(String id) {
- this.id = id;
- }
-
- public String getName() {
- return name;
- }
-
- public void setName(String name) {
- this.name = name;
- }
-
- @Override
- public String toString() {
- return "id:" + id + "---name:" + name;
- }
-
- }
这个用户POJO类很简单,就只有id和name属性。
下面是使用池工厂管理程序
Java代码
- /**
- * 池化工厂
- * @author liuyan
- */
- class KeyedPoolableObjectFactorySample extends BaseKeyedPoolableObjectFactory {
-
- /**
- * 创建对象方法
- */
- @SuppressWarnings("unchecked")
- public Object makeObject(Object clsName) throws Exception {
-
- if (clsName == null || !(clsName instanceof String)
- || "".equals(clsName)) {
- throw new RuntimeException("类名为空!");
- }
-
- System.out.println("创建一个新的对象:" + clsName);
-
- Class cls = Class.forName((String) clsName);
- Object obj = cls.newInstance();
- return obj;
- }
-
- @Override
- public void activateObject(Object key, Object obj) throws Exception {
- // TODO Auto-generated method stub
- super.activateObject(key, obj);
- System.out.println("激活对象");
- }
-
- @Override
- public void destroyObject(Object key, Object obj) throws Exception {
- // TODO Auto-generated method stub
- super.destroyObject(key, obj);
- System.out.println("销毁对象");
- }
-
- @Override
- public void passivateObject(Object key, Object obj) throws Exception {
- // TODO Auto-generated method stub
- super.passivateObject(key, obj);
- System.out.println("挂起对象");
- }
-
- @Override
- public boolean validateObject(Object key, Object obj) {
- // TODO Auto-generated method stub
- System.out.println("验证对象");
- return super.validateObject(key, obj);
-
- }
-
- }
池工厂对象就像是图书馆的调度管理员,需要创建新对象的时候就调用其makeObject()方法;其他的方法已经在程序中进行了说明。主要就是验证对象方法validateObject是验证是否已经存在了可复用的对象。在此历程中采用反射机制对目标对象进行实例化。
下面是使用程序
Java代码
- /**
- * 使用
- * @author liuyan
- *
- */
- public class KeyedObjectPoolSample {
-
- /**
- * @param args
- */
- public static void main(String[] args) {
-
- Object obj = null;
- KeyedPoolableObjectFactory factory = new KeyedPoolableObjectFactorySample();
-
- KeyedObjectPoolFactory poolFactory = new StackKeyedObjectPoolFactory(
- factory);
-
- KeyedObjectPool pool = poolFactory.createPool();
-
- String key = null;
-
- try {
-
- key = "java.lang.String";
-
- obj = pool.borrowObject(key);
- obj = "obj1";
- // pool.returnObject(key, obj);
- obj = pool.borrowObject(key);
- pool.returnObject(key, obj);
- obj = pool.borrowObject(key);
- System.out.println(obj);
-
- System.out.println("============看另一个对象Person=============");
-
- key = "key.Person";
-
- Person person1 = (Person) pool.borrowObject(key);
-
- person1.setId("1");
- person1.setName("素还真");
- System.out.println(person1);
- pool.returnObject(key, person1);
- System.out.println(person1);
-
- Person person2 = (Person) pool.borrowObject(key);
- person2.setId("2");
- person2.setName("青阳子");
-
- Person person3 = (Person) pool.borrowObject(key);
- person3.setId("3");
- person3.setName("一页书");
-
- Person person4 = (Person) pool.borrowObject(key);
- person4.setId("4");
- person4.setName("业途灵");
-
- System.out.println(person1);
- System.out.println(person2);
- System.out.println(person3);
- System.out.println(person4);
-
- pool.returnObject(key, person3);
- Person person5 = (Person) pool.borrowObject(key);
- System.out.println(person5);
-
-
- } catch (Exception e) {
- e.printStackTrace();
- } finally {
- try {
- pool.close();
- System.out.println(pool);
- } catch (Exception e) {
- e.printStackTrace();
- }
- }
-
- }
-
- }
首先先建立一个字符串对象,引用值为 “obj1”。接着从对象池中借一个字符串对象出来obj指向这个借出来的对象,若池中有可复用的对象,直接给之,如果没有,调用makeObject()造一个新的对象出来给obj引用。用完后调用returnObject方法将对象还给池子。咱们再看下面使用Person对象,先从池中借一个Person对象person1。当然池子里面现在还没有任何Person对象。对池中对象赋值后,还给了池子。这个时候池子中就有一个被使用过的,并且属性已经发生改变的对象。下面person2变量向池子借了一个Person对象,此时池子里已经有了一个可复用的对象。那么不必调用makeObject方法,直接将已有的对象返回给客户端即可。然后对其赋值。把刚刚的1素还真改成了2青阳子,那么这个时候刚才的person1也是2青阳子,person1的引用指针没变,仅仅是引用的那块区域的字符串值发生了变化。之后还没等这个对象返还给池子呢,person3变量就向池子要对象,池子说:“等等,我给你make一个啊”,之后又调用了markObject方法创建了一个新的对象。这个新的对象就是3一页书。往下的4业途灵也是新的。当执行到
Java代码
- pool.returnObject(key, person3);
- Person person5 = (Person) pool.borrowObject(key);
- System.out.println(person5);
发现person5是刚刚归还的复用对象——3一页书。执行后控制台如下
Java代码
- 创建一个新的对象:java.lang.String
- 激活对象
- 验证对象
- 创建一个新的对象:java.lang.String
- 激活对象
- 验证对象
- 验证对象
- 挂起对象
- 激活对象
- 验证对象
- ~~~
- ============看另一个对象Person=============
- 创建一个新的对象:key.Person
- 激活对象
- 验证对象
- id:1---name:素还真
- 验证对象
- 挂起对象
- id:1---name:素还真
- 激活对象
- 验证对象
- 创建一个新的对象:key.Person
- 激活对象
- 验证对象
- 创建一个新的对象:key.Person
- 激活对象
- 验证对象
- id:2---name:青阳子
- id:2---name:青阳子
- id:3---name:一页书
- id:4---name:业途灵
- 验证对象
- 挂起对象
- 激活对象
- 验证对象
- id:3---name:一页书
- org.apache.commons.pool.impl.StackKeyedObjectPool contains 0 distinct pools:
经过这个例子我们还应该清楚一个事实就是像这个可对局部变量访问(get)、赋值(set)的对象也不适合池化。因为别人用过了这样的对象,实际上留下了别人用过的痕迹。这样确实会造成局部变量用起来会不一致的线程安全问题。应该是对外仅仅提供服务的方法,并且不具备访问局部变量的对象,并且创建时十分消耗资源的对象才适合池化。到底是池化还是单例,这个当然没有绝对的答案,一切得看您的实际系统需要怎样做更合理。