【并发编程】享元模式

       个人主页:五敷有你      
 系列专栏:并发编程
⛺️稳重求进,晒太阳

享元模式

【并发编程】享元模式_第1张图片

简介

定义 英文名称:Flyweight pattern. 当需要重用数量有限的同一类对象时

        享元模式是一种结构型的设计模式。它的主要目的是通过共享对象来减少系统种对象的数量,其本质就是缓存共享对象,降低内存消耗。

        享元模式将需要重复使用的对象分为两个部分:内部状态和外部状态。

        内部状态是不会变化的,可以被多个对象共享,而外部状态会随着对象的使用而改变。比如,连接池中的连接对象,保存在连接对象中的用户名、密码、连接URL等信息,在创建对象的时候就设置好了,不会随环境的改变而改变,这些为内部状态。而当每个连接要被回收利用时,我们需要将它标记为可用状态,这些为外部状态。

体现

包装类

在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
  • Integer的默认值是-128~127
    • 最小值不能变
    • 但最大值可以通过调整虚拟机参数
      • -Djava.lang.Integer.IntegerCache.high 来改变
  • Boolean缓存了TRUE 和 FALSE

String 串池

        String类是final修饰的,以字面量的形式创建String变量时,JVM会在编译期间就把该字面量“hello”放到字符串常量池中,由Java程序启动的时候就已经加载到内存中了。这个字符串常量池的特点就是有且只有一份相同的字面量,如果有其它相同的字面量,JVM则返回这个字面量的引用,如果没有相同的字面量,则在字符串常量池创建这个字面量并返回它的引用。

BigDecimal BigInteger

注意之前的BigDecimal是线程安全的为什么之前还要使用AtomicReference呢,因为

之前虽然他是安全的,但是那个是三条指令,每条指令的原子性不能保证三条指令的原子性,所以加入Atomic来CAS操作‘

DIY

例如:一个线上商城应用,QPS 达到数千,如果每次都重新创建和关闭数据库连接,性能会受到极大影响。 这时预先创建好一批连接,放入连接池。一次请求到达后,从连接池获取连接,使用完毕后再还回连接池,这样既节约了连接的创建和关闭时间,也实现了连接的重用,不至于让庞大的连接数压垮数据库。

class Pool {
    // 1. 连接池大小
    private final int poolSize;
    // 2. 连接对象数组
    private Connection[] connections;
    // 3. 连接状态数组 0 表示空闲, 1 表示繁忙
    private AtomicIntegerArray states;
    // 4. 构造方法初始化
    public Pool(int poolSize) {
        this.poolSize = poolSize;
        this.connections = new Connection[poolSize];
        this.states = new AtomicIntegerArray(new int[poolSize]);
        for (int i = 0; i < poolSize; i++) {
            connections[i] = new MockConnection("连接" + (i+1));
        }
    }
    // 5. 借连接
    public Connection borrow() {while(true) {
        for (int i = 0; i < poolSize; i++) {
            // 获取空闲连接
            if(states.get(i) == 0) {
                if (states.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) {
                states.set(i, 0);
                synchronized (this) {
                    log.debug("free {}", conn);
                    this.notifyAll();
                }
                break;
            }
        }
    }
}
class MockConnection implements Connection {
    // 实现略
}

使用连接池

Pool pool = new Pool(2);
for (int i = 0; i < 5; i++) {
    new Thread(() -> {
        Connection conn = pool.borrow();
        try {
            Thread.sleep(new Random().nextInt(1000));
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        pool.free(conn);
    }).start();
}

        以上实现没有考虑:连接的动态增长与收缩连接保活(可用性检测)等待超时处理分布式 hash对于关系型数据库,有比较成熟的连接池实现,例如c3p0, druid等 对于更通用的对象池,可以考虑使用apachecommons pool,例如redis连接池可以参考jedis中关于连接池的实现

Final原理

public class TestFinal { 
    final int a = 20;
}

字节码

0: aload_0
1: invokespecial #1 // Method java/lang/Object."":()V
4: aload_0
5: bipush 20
7: putfield #2 // Field a:I
 <-- 写屏障
10: return

发现 final 变量的赋值也会通过 putfield 指令来完成,同样在这条指令之后也会加入写屏障,保证在其它线程读到它的值时不会出现为 0 的情况

无状态

在web阶段学习时,设计Servlet时为了保证线程安全,都会有这样的建议,不要为Servlet设置成员变量,这种没有成员变量的类是线程安全的。

因为成员变量保存的数据也可以称为状态信息。因此没有成员变量就称为无状态、

你可能感兴趣的:(并发编程,享元模式,java,开发语言)