对象池模式

一种特殊的工厂对象,一个对象池包含一组已经初始化过且可以使用的对象。用户可以从池中取得对象,并对其进行操作,在不需要的时候将其归还给对象池。

使用对象池的目的是为了提升性能,当需要创建比较耗费时间的对象时尤为明显。

对象池通用类图

角色和职责

对象池的管理者

管理者来维护对象池,包括初始化对象池、扩充对象池的大小、获取对象时的策略、重置归还对象的状态。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
public class PoolMgr{

private int init ;

private int max;

private int factor;

private Colection c; //

public void init(){
//init
}
//根据策略来动态的生成对象。。
//如果对象不足可以增加对象池的对象,或者blocking直到有对象释放。
public Object aquire(){

return c.remove();
}

public void release(Object o){
if(o == null) return;

reset(o);
c.add(o);
}

}

对象池的使用者

使用者从管理者处获取对象,使用完成后必须归还对象避免一致占用。

1
2
3
4
5
6
Object o = null;
try{
o = PoolMgr.getInstance();
}finaly{
PoolMgr.release(o);
}

优点

复用池中的对象,消除创建/回收对象所产生的内存开销,CPU开销以及IO开销。
常见的有:Socket的链接池、线程池、数据库连接池等。

缺点

多线程并发环境中,需要考虑线程安全问题,需要在数据结构上进行同步或者为锁竞争产生阻塞,这种开销处理不好会比创建销毁独享的开销高很多。
由于池中的对象数量有限,对象池小可能会成为性能瓶颈,太大占用内存资源高;
轻量级对象的创建/销毁不需要使用对象池,徒增系统的复杂度;

Common Pool2

Commons Pool2核心部分由三个基础接口和相关的实现类实现:

PooledObject

池化对象,对池中的对象进行封装,封装对象的状态和一些其他信息。
PooldedObject 有两个实现类,DefaultPoolObject时普通通用的实现,PooledSoftReference使用SoftReference封装了对象,供SoftReferenceObjectPool使用。

池化对象类图

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
public interface PooledObject<T> extends Comparable<PooledObject<T>> {
// 获取封装的对象
T getObject();

// 对象创建的时间
long getCreateTime();

// 对象上次处于活动状态的时间
long getActiveTimeMillis();

// 对象上次处于空闲状态的时间
long getIdleTimeMillis();

// 对象上次被借出的时间
long getLastBorrowTime();

// 对象上次返还的时间
long getLastReturnTime();

// 对象上次使用的时间
long getLastUsedTime();

// 将状态置为 PooledObjectState.INVALID
void invalidate();

// 更新 lastUseTime
void use();

// 获取对象状态
PooledObjectState getState();

// 将状态置为 PooledObjectState.ABANDONED
void markAbandoned();

// 将状态置为 PooledObjectState.RETURNING
void markReturning();
}

PooledObjectFactory

池化对象工厂,负责对象的创建、初始化、销毁和验证工作,Factory对象由ObjectPool持有并使用。
因为对象的创建、初始化、销毁和仰正的工作无法通用,Commons Pool2并没有提供PooledObjectFactory的默认子类实现,只提供了抽象子类BasePooledObjectFactory
可以实现create和wrap两个方法。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
public interface PooledObjectFactory<T> {
// 创建一个池对象
PooledObject<T> makeObject() throws Exception;

// 销毁对象
void destroyObject(PooledObject<T> p) throws Exception;

// 验证对象是否可用
boolean validateObject(PooledObject<T> p);

// 激活对象,从池中取对象时会调用此方法
void activateObject(PooledObject<T> p) throws Exception;

// 钝化对象,向池中返还对象时会调用此方法
void passivateObject(PooledObject<T> p) throws Exception;
}

ObjectPool

持有对象并提供取/还等操作.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
 public interface ObjectPool<T> {
// 从池中获取一个对象,客户端在使用完对象后必须使用 returnObject 方法返还获取的对象
T borrowObject() throws Exception, NoSuchElementException,
IllegalStateException;

// 将对象返还到池中。对象必须是从 borrowObject 方法获取到的
void returnObject(T obj) throws Exception;

// 使池中的对象失效,当获取到的对象被确定无效时(由于异常或其他问题),应该调用该方法
void invalidateObject(T obj) throws Exception;

// 池中当前闲置的对象数量
int getNumIdle();

// 当前从池中借出的对象的数量
int getNumActive();

// 清除池中闲置的对象
void clear() throws Exception, UnsupportedOperationException;

// 关闭这个池,并释放与之相关的资源
void close();

...
}

对象池配置

对象池配置提供了对象池初始化所需要的参数,Common Pool2中的基础配置类时BaseObjectPoolConfig, 有GenericObjectPoolConfig和GenericKeyedObjectPoolConfig,分别为GenericObjectPool和GenericKeyedObjectPool使用。

参数 解释 默认值
lifo 连接池存放池对象的方式; true:放在空闲队列的嘴钱买呢, false:放在空闲队列的最后面 true
fairness 从池中获取/返还对象时是否使用公平锁机制 false
maxWaitMillis 获取资源的等待时间。blockWhenExhausted为true时有效。-1代表无时间限制,一致阻塞到有可用的资源
minEvictableIdleTimeMillis 对象空闲的最小时间,打到此值后空闲对象可能会被移除,-1表示不移除 30分钟
softMinEvictableIdletimeMillis 对象空闲的最小时间,并且池中之少保留有minidle所指定的个数
numTestsPerEvictionRun 资源回收线程执行一次回收操作回收资源的个数 3
evictionPolicyClassName 资源回收策略 org.apache.commons.pool2.impl.DefaultEvictionPolicy
testOnCreate 创建对象时是否调用factory.validateObject false
testOnBorrow 获取对形势是否调用factory.validateObject false
testOnReturn 返还对象时是否调用factory.validateObject false
timeBetweenEvictionRunsMills 回收资源线程的执行周期,默认-1表示不启用回收资源线程 -1
blockWhenExhausted 资源耗尽时,是否阻塞等待获取资源 true
testWhileIdel 池中的限制对象是否由逐出器验证,无法验证的对象将从池中删除销毁 false

池化对象的状态

参数值 描述
IDEL 在池中,处于空闲状态
ALLOCATED 被使用中
EVICTION 正在被逐出器验证
VALIDAITTON 正在验证
INVALID 驱逐测试或者验证失败并将被销毁
ABANDONED 对象被客户端拿出后,长时间未返回池中,或者没有调用use方法,被标记为抛弃

Demo

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
public class StringBufferFactory extends BasePooledObjectFactory<StringBuffer> {
// 创建一个新的对象
@Override
public StringBuffer create() {
return new StringBuffer();
}

// 封装为池化对象
@Override
public PooledObject<StringBuffer> wrap(StringBuffer buffer) {
return new DefaultPooledObject<>(buffer);
}

// 使用完返还对象时将 StringBuffer 清空
@Override
public void passivateObject(PooledObject<StringBuffer> pooledObject) {
pooledObject.getObject().setLength(0);
}
}

使用

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
 // 创建对象池配置
GenericObjectPoolConfig config = new GenericObjectPoolConfig();
// 创建对象工厂
PooledObjectFactory factory = new StringBufferFactory();
// 创建对象池
ObjectPool<StringBuffer> pool = new GenericObjectPool<>(factory, config);
StringReader in = new StringReader("abcdefg");
StringBuffer buf = null;
try {
// 从池中获取对象
buf = pool.borrowObject();

// 使用对象
for (int c = in.read(); c != -1; c = in.read()) {
buf.append((char) c);
}
return buf.toString();
} catch (Exception e) {
try {
// 出现错误将对象置为失效
pool.invalidateObject(buf);
// 避免 invalidate 之后再 return 抛异常
buf = null;
} catch (Exception ex) {
// ignored
}
throw e;
} finally {
try {
in.close();
} catch (Exception e) {
// ignored
}

try {
if (null != buf) {
// 使用完后必须 returnObject
pool.returnObject(buf);
}
} catch (Exception e) {
// ignored
}
}

参考:
Apache Common Pool2 对象池应用浅析
一个广为人知但鲜有人用的技巧:对象池

原型模式

不通过new关键字来产生一个对象,而通过对象复制来生产对象的模式,原型模式的核心是clone方法,通过该方法进行对象的拷贝。

也就是先生产出一个包含大量共有信息的类对象,然后拷贝出副本修正细节信息,建立一个完整的个性对象。

定义

用原型实例指定创建对象的种类,并且通过拷贝这些原型创建新的对象

原型模式通用类图

优缺点

  • 性能好
    原型模式是内存二进制流侧拷贝,要比直接new一个对象性能好很多,特别在一个循环体内产生大量的对象时,原型模式可以更好的体现其优点。
  • 逃避构造函数的约束
    直接在内存中拷贝,构造函数不会执行。优点和缺点都是减少了约束。

使用场景

需要频繁的new一个对象,并且该对象需要繁琐的数据准备时;
一个对象要提供给其他对象访问,各个调用者可能需要修改其值时;

原型模式一般与工厂方法模式一起出现,通过clone的方法来创建对象,然后由工厂方法提供给调用者。

实现

1
2
3
4
5
6
7
8
9
10
11
12
public class PrototypeClass implements Cloneable{
@Override
public PrototypeClass clone(){
PrototypeClass p = null;
try{
p = (PrototypeClass)super.clone();
}catch(CloneNotSupportedException e){
//TODO
}
return p;
}
}

注意事项

构造函数不会被执行

Object类的clone方法原理是从内存中(堆内存)以二进制流的方式进行拷贝,重新分配一个内存快,构造函数是不会被执行的。

浅拷贝

Object类提供的方法clone只是拷贝本对象,对于其内部的数组、引用对象都不拷贝还是指向原声对象的内部元素地址。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
public class PrototypeClass implements Cloneable{
public ArrayList<String> arrayList = new ArrayList<>();

@Override
public PrototypeClass clone(){
PrototypeClass p = null;
try{
p = (PrototypeClass) super.clone();
}catch(CloneNotSupportedException e) ) {
//
}
return p;
}
}

深拷贝

深拷贝需要拷贝对象自身外,还需要拷贝对象的所有引用的对象。

通过clone方式实现:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
public class PrototypeClass implements Cloneable{
public ArrayList<String> arrayList = new ArrayList<>();

@Override
public PrototypeClass clone(){
PrototypeClass p = null;
try{
p = (PrototypeClass) super.clone();
p.arrayList = (ArrayList<String>) this.arrayList.clone();
}catch(CloneNotSupportedException e) ) {
//
}
return p;
}
}

通过序列化方式来实现,通过序列化将对象(该对象的类必须实现Serializable接口)写到一个流中,然后再从流里把对象读出来以实现深克隆。

1
2
3
4
5
6
7
8
9
public static <T extends Serializable> T clone(T t) throws Exception {
ByteArrayOutputStream baos = new ByteArrayOutputStream();
ObjectOutputStream oos = new ObjectOutputStream(baos);
oos.writeObject(t);

ByteArrayInputStream bais = new ByteArrayInputStream(baos.toByteArray());
ObjectInputStream ois = new ObjectInputStream(bais);
return (T)ois.readObject();
}

clone与final的冲突

当使用clone方法的时候,类的成员变量不可以增加final关键字。

拓展:
Java提高篇——对象克隆(复制)

建造者模式

建造者模式与工厂模式关注点不同,建造者模式关注零件的组装过程,工厂模式注重零件的创建过程。

定义

建造者模式也叫生成器模式, 它将一个复杂对象的构建与它的表示分离,使得同样的建造构建过程可以创建不同的表示。

建造者模式通用类图

优点

  • 封装性良好
    使用建造者模式可以使得客户端不需要知道产品内部组成的细节。
  • 建造者独立,易扩展
    不同的实现类Builder是相互独立的,对系统的扩展有利。
  • 便于控制细节风险
    由于建造者独立,因此可以个性化,修改一个建造者不会对其他模块产生影响。

缺点

  • 产品的组成部分必须相同,限制了其使用范围
  • 产品内部变化复杂时,需要增加很多建造者类

使用场景

  • 相同的方法,不同的执行顺序,产生不同的事件结果时;
  • 多个部件或者零件都可以装配到一个对象中,但是产生的运行结果不同时;
  • 产品类非常复杂,或者产品类中的调用顺序不同产生了不同的效能;

实现

Product 产品类

1
2
3
4
5
public class Product{
public void doSomething(){

}
}

Builder抽象建造者

1
2
3
4
public abstract class Builder{
abstract void setPart(); //设置个性部分
abstract Product buildProduct();//建造产品
}

ConcreteBuilder 具体建造者

1
2
3
4
5
6
7
8
public class ConcreteBuilder extends Builder{
public void setPart(){
//...
}
pbulic Product buildProduct(){
return new Product();
}
}

Director 导演类

导演类起到封装的作用,避免高层模块深入到建造者内部的实现类。当建造者模式比较庞大时,导演类可以有多个。

1
2
3
4
5
6
7
8
9
public class Director{

public Product buildProductA(){
Builder b = new ConcreteBuilder();
b.setPart();
return b.buildProduct();
}

}

抽象工厂模式

抽象工厂模式是工厂方法模式的升级版本,工厂方法模式只生产一个等级的产品,抽象工厂模式可以生产多个等级的产品。

概念

产品等级:不同产品的种类就是不同的产品等级(电视机和洗衣机就是不同的产品等级)
产品族:一个具体工厂生产的不同等级的产品(工厂生产的电视机和洗衣机属于一个产品族)

产品等级和产品族

定义

一种为访问类提供一个创建一组相关或者相互依赖对象的接口,且访问类无需指定所需要产品的具体类就能得到同族的不同等级的产品。

抽象工厂模式类图

使用场景

1) 客户端不依赖于产品类实例如何被创建、实现
2) 强调一系列相关的产品(同一产品族)一起使用创建对象需要大量的代码
3) 提供一个产品类的库,所有的产品以同样的接口出现,从而使客户端不依赖于具体实现。
一个对象族都有相同的约束,则可以使用抽象工厂模式。

实现

产品角色实现

抽象产品:Product,定义产品的规范,描述产品的主要特性和功能,抽象工厂模式有多个抽象产品。
具体产品:ConcreteProduct,实现抽象产品角色所定义的接口由具体工厂来创建,与具体工厂之间是一对多的关系。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
public abstract Product{
//all product base filds & methods.
abstract int on();
abstract int off();
}

public abstract AbstractTvProduct extends Product{
abstract int switchChannel();
}

public abstract AbstractFridgeProduct extends Product{
abstract int increaseTemp();
abstract int decreaseTemp();
}

public HaierTVProduct extends AbstractTvProduct{

}

public HaierFridgeProduct extends AbstractFridgeProduct{

}

public TCLTVProduct extends AbstractTvProduct{

}

public TCLFridgeProduct extends AbstractFridgeProduct{

}

工厂角色实现

抽象工厂:Abstract Factory,提供了创建产品的接口,包含多个创建产品的方法,可以创建多个不同等级的产品。
具体工厂:实现抽象工厂中的多个抽象方法,完成具体产品的创建。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
public interface IFactory{
Product createTV();
Product createFridge();
}


public class HaierFactory implements IFactory{
Product createTV(){
return new HaierTVProduct();
}
Product createFridge(){
return new HaierFridgeProduct();
}
}

public class TCLFactory implements IFactory{
Product createTV(){
return new TCLTVProduct();
}
Product createFridge(){
return new TCLFridgeProduct();
}
}

优缺点

  • 隔离了具体类的生成,使得客户并不需要知道什么被创建,具有良好的封装性。
  • 如果要扩展产品族(有新的工厂要生产电视和冰箱时)的时候,只需要增加具体的产品类和实现工厂方法即可,原有的代码不需要变动,符合开闭原则。
  • 如果要扩展产品等级时,从抽象产品到工厂类,原有的代码都需要变动,违背了开闭原则。

参阅:
抽象工厂模式(详解版)

场景

  • new 对象的替代方案
  • 需要灵活、可扩展的框架时
  • 在异构项目中,从外部产生的对象
  • 测试驱动开发框架下使用。测试驱动开发 -TDD

工厂方法模式

定义

定义一个用于创建对象的接口,让子类决定实例化哪一个类。工厂方法使一个类的实例化延迟到其子类。

工厂方法模式的通用类图

抽象产品类Product负责定义产品的共性,实现对事物最抽象的定义;
Creator为抽象的创建类,即抽象工厂,具体如何创建产品类是由具体的实现工厂ConcreteCreator完成的。

优点

  • 良好的封装,代码结构清晰,降低模块间的耦合;
  • 良好的扩展性;
  • 屏蔽具体的产品类,只要接口不变,系统中上层模块就不需要发生变化。

高层模块只需要知道产品的抽象类,不关心实现类,符合迪米特法则;
只依赖产品的抽象,符合倒置原则;
使用产品子类替换父类时也没有问题,符合里氏替换原则;

缺点

对于不同类型的产品都要增加不同类型的工厂来管理,增加代码的复杂性

实现

通用实现

产品类:

1
2
3
4
5
6
7
8
9
10
11
12
public abstract class Product{
public abstract void method1();
public void method2(){}
}

public class Product1 extends Product{
public void method1(){}
}

public class Product2 extends Product{
public void method1(){}
}

工厂类:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
public abstract class Factory{
public abstract <T extends Product> T create(Class<T> c);
}

public class ConcreteFactory extends Factory{

public <T extends Product> T create(Class<T> c){
Product p = null;
try{
p = (Product)Class.forName(c.getName()).newInstance();
}catch(Exception e){

}
return (T)p;
}
}

使用:

1
2
Factory factory = new ConcreteFactory();
Product p = factory.createt(Product1.class);

缩小为简单工厂

去掉抽象的Factory类,create()方法变为静态方法即可。

1
2
3
4
5
6
7
8
9
10
11
12
public void Factory{

public static <T extends Product> T create(Class<T> c){
Product p = null;
try{
p = (Product)Class.forName(c.getName()).newInstance();
}catch(Exception e){

}
return (T)p;
}
}

调用代码:

1
Product p = Factory.create(Product1.class);

升级为多个工厂类

遇到复杂情况下,一个工厂类初始化需要耗费很多资源,这样可以将工厂类细化拆分。

1
2
3
4
5
6
7
8
9
10
11
12
public class Factory1 extends Factory{
public static Product create(){
return new Product1();
}

}

public class Factory2 extends Factory{
public static Product create(){
return new Product2();
}
}

这样的好处是职责清晰,结构简单,但是可扩展性和可维护性差了些。
一般情况下,再增加一个协调类,避免调用者直接与各个子工厂类交流,协调类的作用是封装子工厂类,对高层模块提供统一的访问接口。

替代单例模式

通过反射的方式来创建唯一一个实例

延迟初始化

一个对象被使用完毕后,并不立即释放,工厂类保持其初始状态,等待再次被使用。

延迟加载框架可以被扩展为,限制某个产品类的最大实例化数量(如链接池的最大链接数量)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
public class ProductFactory{

private static final Map<String,Product> pMap = new HashMap();

public static synchronized Product createProduct(String type) throws Exception{
Product p = null;
if(pMap.containsKey(type)){
p = pMap.get(type);
}else{
if(type.equals("Product1")){
p = new Product1();
}else{
p = new Product2();
}
}
return product;
}
}

定义一个用于创建对象的接口,让子类决定实例化哪一个类。工厂方法使一个类的实例化延迟到其子类。

https://www.zhihu.com/question/24843188

https://www.jianshu.com/p/83ef48ce635b

https://blog.csdn.net/buyulian/article/details/79203880

https://blog.csdn.net/qianhaifeng2012/article/details/52014773

https://www.cnblogs.com/aspirant/p/8980573.html

http://www.akathink.com/2016/08/01/%E5%BD%BB%E5%BA%95%E6%90%9E%E6%87%82%E5%B7%A5%E5%8E%82%E6%A8%A1%E5%BC%8F/

单例模式

在一个系统中,要求一个类有且仅有一个对象,如果出现多个对象就会出现“混乱”。

  • 要求生成唯一序号的场景
  • 整个项目中需要一个共享数据,如计数器
  • 创建对象要消耗过多资源(IO访问或者数据库资源等)
  • 定义大量的静态常量和静态方法的工具类

定义

确保一个类只有一个实例,而且自行实例化并且向整个系统提供这个实例。

优点

单例模式在内存中只有一个实例,减少了内存开支。
当一个对象需要频繁地创建、销毁时,而且创建或者销毁时的性能无法优化时,单例模式的优势非常明显。

缺点

没有接口,无法扩展,没有接口也不能使用Mock方式虚拟对象。
在并行开发环境中,单例模式没有完成无法进行测试。
与单一职责原则冲突,单例模式把业务逻辑和单例混合在一个类中。

注意事项

高并发情况下,单例模式的线程同步问题。

提前加载

1
2
3
4
5
6
7
8
9
10
public class Singleton{

private static Singleton instance = new Singleton();

private Singleton(){}

public static Singleton getInstance(){
return instance;
}
}

延后加载

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
public class Singleton{

private volatile static Singleton instance = null;

private Singleton(){}

public static Singleton getInstance(){
if(instance == null){
synchronized(Singleton.class){
if(instance == null){
instance = new Singleton();
}
}
}
return instance;
}

}

枚举

1
2
3
public enum Singleton{
instance;
}