持续创作,加速成长!这是我参与「掘金日新计划 · 6 月更文挑战」的第28天,点击查看活动详情。
享元模式的目的是为了减少不必要的频繁创建对象,从而造成性能和内存的浪费,通过对象共享的方式提高资源利用率。在生活中公共交通、共享经济(共享单车、共享充电保)、合租等等都是享元对象的体现。
享元模式
享元模式(Flyweight Pattern)主要目的是实现对象的共享,减少创建对象的数量,以减少内存占用和提高性能,一般会配合工厂模式使用。这种类型的设计模式属于结构型模式。
和单例模式不同,享元模式强调对象共享而不是唯一。
主要就是将相似的对象放入缓存,在下次需要的时候,如果可以从缓存中拿,则直接拿,如果不可以则新建对象,并将对象放入缓存。
java中享元模式的应用:
- String 字符串池化技术
- 基本数据类型池化技术
- 数据库连接池
简单证明
String的池化技术
在java中使用String str = XXX创建字符串,jvm会检测字符串缓存池存不存在这个“abc”,不存在则创建一个并将创建的 “abc”字符串对象放入字符串缓存池,如果下次还是使用String str = XXX的方式创建相同字符串则不会重新创建而是去字符串缓存区拿。
注意String str = XXX 和 String str = new String(XXX)是不同的。new String(XXX)他会在堆内存空间中创建一个独立的String对象而不会存入缓冲区。
@Test
public void testString(){
String str1 = "abc";
String str2 = "123";
String str3 = "abc";
String str4 = new String("123");
String str5 = new String("123");
/**
* true
* str1 = “abc”
* str3 = "abc" 检测到存在字符串“abc”,直接将str3引用指向缓存池中的“abc”
*/
System.out.println(str1 == str3);
/**
* false
* String str4 = new String("123"); str4会在堆内存创建一个String对象,堆内存的和缓存池的肯定不相等
*/
System.out.println(str2 == str4);
/**
* false
* new String("123") 在堆内存创建了一个新的字符串对象
*/
System.out.println(str4 == str5);
}
基本数据类型的包装类型的池化技术,以Integer为例
integer的缓存范围为 【-128,127】,在这之间的Integer都会放入缓存池,超过这个范围当作普通对象处理。jvm理解这个范围的数据比较常用。
@Test
public void testInteger(){
Integer int1 = Integer.valueOf(1);
Integer int2 = Integer.valueOf(1);
Integer int7 = Integer.valueOf(128);
Integer int8 = Integer.valueOf(128);
Integer int9 = Integer.valueOf(-129);
Integer int10 = Integer.valueOf(-129);
//存在于缓存范围,返回true
System.out.println(int1 == int2);
//不存在于缓存范围,当作普通对象处理,返回false
System.out.println(int7 == int8);
System.out.println(int9 == int10);
}
内部状态 & 外部状态
内部状态,就是可以共享的,不可变的属性。 外部状态,会随着环境的变化而变化
比如对于数据库连接池技术来说,一个Connection包含driverClassName、UserName、Password和dbname,这些属性对于连接池来说都是一样的,不会随外部环境变化,也就是内部属性。其他作为外部属性。
实现
定义抽象享元类:
public interface IConnection {
/**
* 获取用户姓名
* @return
*/
String getUserName();
/**
* 释放资源
*/
void release();
}
定义具体享元类
@Getter
@Setter
@AllArgsConstructor
public class MyConnection implements IConnection{
String userName;
ConnectionPoll connectionPoll;
@Override
public String getUserName() {
return this.userName;
}
@Override
public void release() {
connectionPoll.add(this);
}
}
定义享元工厂:
public class ConnectionPoll {
/**
* 内部属性
*/
private static String userName = "RolyFish";
/**
* 内部属性
*/
private static String passWord = "123";
private Integer pollSiz = 10;
private ConcurrentLinkedQueue<IConnection> poll = new ConcurrentLinkedQueue<>();
{
//初始化连接池
for (int i = 0; i < 3; i++) {
poll.add(new MyConnection(10, passWord, userName, this));
}
}
public IConnection getConnection(Integer timeOut) {
if (poll.size() > 0) {
return poll.remove();
} else {
poll.add(this.createInstance(timeOut));
return getConnection(timeOut);
}
}
public void add(IConnection connection){
poll.add(connection);
}
public IConnection getConnection() {
return getConnection(10);
}
private IConnection createInstance(Integer timeOut) {
return new MyConnection(timeOut, ConnectionPoll.userName, ConnectionPoll.passWord, this);
}
private IConnection createInstance() {
return createInstance(10);
}
}
测试:
@Test
public void test(){
ConnectionPoll connectionPoll = new ConnectionPoll();
for (int i = 0; i < 10; i++) {
IConnection connection = connectionPoll.getConnection();
System.out.println(connection);
connection.release();
}
}
可以发现实现了对象重用。
优缺点
优点
- 享元模式可以极大减少内存中对象的数量,使得需要被重复使用的或相似的对象在内存中进行缓存,避免重复 创建新的对象
缺点
- 增加系统复杂性,需要将外部对象和内部对象抽离,且外部对象的不会随内部对象的改变而改变,需要对系统进行整体评估和划分,对业务和抽象能力要求较高,成本较大