享元模式
由于保护性拷贝需要不停的新创建对象,所有提出了享元模式
简介
当需要重用数量有限的同一对象时。通过对相同值的共享,来做到最小化内存的使用。是Gang of Four 23种设计模式之一
体现
包装类
在JDK中Boolean,Byte,Short,Integer,Long,Character等包装类提供了valueOf方法,例如Long的valueOf会缓存-128~127之间的Long对象,在这个范围之间会重用对象,大于这个范围,才会新建Long对象:
public static Long valueOf(long l) {
final int offset = 128;
if (l >= -128 && l <= 127) { // will cache
return LongCache.cache[(int)l + offset];
}
return new Long(l);
}
注意:
Byte,Short,Long缓存的范围都是-128~127
Character缓存的范围是0~127
Interger的默认范围是-128~127,最小值不能变,但最大值可以通过调整虚拟机参数-Djava.long.Integer.IntegerCache.high来改变
Boolean缓存了TRUE和FALSE
Sting串池
BigDecimal BigInteger
这些类中的方法虽然是线程安全的,但当方法组合使用时,还是会出现线程安全的问题
DIY
例如:一个线上商城应用,QPS达到数千,如果每次都重新创建和关闭数据库连接,性能会受到极大影响。这时预先创建好一批连接,放入连接池。一次请求到达后,从连接池获取连接,使用完毕后再还回连接池,这样既节约了连接的创建和关闭时间,也实现了连接的重用,不至于让庞大的连接数压垮数据库。
@Slf4j(topic = "c.SharePoolDemo")
public class SharePoolDemo {
public static void main(String[] args) {
Pool pool = new Pool(2);
for (int i = 0; i < 5; i++) {
new Thread(()->{
Connection borrow = pool.borrow();
try {
//模拟线程工作
Thread.sleep(new Random().nextInt(1000));
} catch (InterruptedException e) {
e.printStackTrace();
}finally {
pool.free(borrow);
}
}).start();
}
}
}
@Slf4j(topic = "c.Pool")
class Pool{
// 1.连接池大小
private final int poolSize;
// 2.连接对象数组
private Connection[] connections;
// 3.连接状态数组 0表示空闲 1表示繁忙(这里要保证数组内元素线程安全)
private AtomicIntegerArray state;
// 4.构造方法
public Pool(int poolSize) {
this.poolSize = poolSize;
this.connections = new Connection[poolSize];
this.state = new AtomicIntegerArray(new int[poolSize]);
for (int i = 0; i < poolSize; i++) {
//这里MockConnection类是继承Connection接口的类
connections[i] = new MockConnection("连接"+(i+1));
}
}
//5.借连接
public Connection borrow(){
while (true){
for (int i = 0; i < poolSize; i++) {
// 获取空闲连接
if(state.get(i)==0){
if(state.compareAndSet(i,0,1)){
log.debug("borrow {}",connections[i]);
return connections[i];
}
}
}
// 如果没有空闲连接,当前线程进入等待
synchronized (this){
try {
log.debug("wait...");
this.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
//6.归还连接
public void free(Connection conn){
for (int i = 0; i < poolSize; i++) {
if (connections[i] == conn) {
state.set(i,0);
synchronized (this){
log.debug("free {}",conn);
this.notifyAll();
}
break;
}
}
}
}
以上没有考虑:
· 连接的动态增长与收缩
· 连接保活(可用性检测)
· 等待超时处理
· 分布式hash
对于关系型数据库,有比较成熟的连接池实现,例如c3p0,druid等
对于更通用的对象池,可以考虑使用apache commons pool,例如redis连接池可以参考jedis中关于连接池的实现