JUC P4 共享模型之不可变,享元模式自定义连接池 基础+代码
教程:https://www.bilibili.com/video/BV16J411h7Rd
8. 共享模型之不可变
- 不可变类的使用
- 不可变类设计
- 无状态类设计
8.1 引例:日期转换问题
@Slf4j(topic = "c.InitTest")
public class InitTest {
public static void main(String[] args) {
SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd");
for (int i = 0; i < 10; i++) {
new Thread(() -> {
try {
log.debug("{}", sdf.parse("2022-10-28"));
} catch (Exception e) {
e.printStackTrace();
}
}).start();
}
}
}
SimpleDateFormat 是可变类,当出现多个线程并发访问的时候会出现问题。
8.1.1 synchronized 加锁解决
SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd");
for (int i = 0; i < 10; i++) {
new Thread(() -> {
synchronized (sdf) {
try {
log.debug("{}", sdf.parse("2022-10-28"));
} catch (Exception e) {
e.printStackTrace();
}
}
}).start();
}
8.1.2 DateTimeFormatter 不可变对象解决
加锁的方式确实可以解决并发问题,因此可以使用不可变对象解决:
@Slf4j(topic = "c.InitTest")
public class InitTest {
public static void main(String[] args) {
DateTimeFormatter dtf = DateTimeFormatter.ofPattern("yyyy-MM-dd");
for (int i = 0; i < 10; i++) {
new Thread(() -> {
try {
log.debug("{}", dtf.parse("2022-10-28"));
} catch (Exception e) {
e.printStackTrace();
}
}).start();
}
}
}
8.2 不可变设计
String 的部分属性:
/** The value is used for character storage.*/
@Stable
private final byte[] value;
/**
* The identifier of the encoding used to encode the bytes in
* {@code value}. The supported values in this implementation are
* LATIN1
* UTF16
*/
private final byte coder;
/** Cache the hash code for the string */
private int hash; // Default to 0
8.2.1 final 的使用
String 类和类中的属性被 final 修饰
- 属性用 final 修改保证该属性是只读的,不能修改
- 类上也被 final 修饰保证该类中的方法不能被覆盖,防止子类无意间破坏不可变性
8.2.2 保护性拷贝
String 中构造方法创建了一个新的字符数组,对内容进行复制。这种通过创建副本对象来避免共享的手段称之为保护性拷贝(Defensive copy)。
8.3 享元模式(Flyweight pattern)
主要用于减少创建对象的数量,以减少内存占用和提高性能。这种类型的设计模式属于结构型模式,它提供了减少对象数量从而改善应用所需的对象结构的方式。
8.3.1 适用条件
当需要重用数量有限的同一类对象时,比如连接池,线程池,常量池等
8.3.2 体现
包装类
- Boolean,Byte,Integer,Character 等包装类提供了 valueOf 方法,会缓存 -128~127 之间的相应包装类的对象,在这个范围之间会重用对象,大于这个返回才会新建新的对象。
public static Integer valueOf(int i) {
if (i >= IntegerCache.low && i <= IntegerCache.high)
return IntegerCache.cache[i + (-IntegerCache.low)];
return new Integer(i);
}
private static class IntegerCache {
static final int low = -128;
static final int high;
static final Integer[] cache;
/*.....*/
}
Note:
各个类型的缓存范围:
- Byte,Short,Long:-128~127
- Character:0~127
- Integer:默认 -128~127,最小值 -128 不能变,最大值可以通过
-XX:AutoBoxCacheMax=<size>
设置- Boolean:TRUE 和 FALSE
另外还有 String 串池,BigDecimal 和 BigInteger
8.4 自定义连接池
例如:高并发业务中访问数据库,如果每次都重新创建和关闭数据库连接池,性能会受到极大影响。
因此我们可以预先创建好一批连接,放入连接池,一次请求到达后直接冲连接池中获取链接,使用完毕后再还回连接池。
这样就实现了连接复用,节约了连接的创建和关闭时间,不至于让庞大的连接数压垮数据库。
@Slf4j(topic = "c.InitTest")
public class InitTest {
public static void main(String[] args) {
Pool pool = new Pool(2);
for (int i = 0; i < 5; i++) {
new Thread(() -> {
Connection conn = pool.borrow();
try {
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
pool.free(conn);
}
}).start();
}
}
}
@Slf4j(topic = "c.Pool")
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(poolSize);
for (int i = 0; i < poolSize; i++) {
connections[i] = new MockConnection("连接对象-" + i);
}
}
// 5. 借连接
public Connection borrow() {
while (true) {
for (int i = 0; i < poolSize; i++) {
// 获取空闲连接
if (states.get(i) == 0 && states.compareAndSet(i, 0, 1)) {
log.debug("Borrow {}", connections[i]);
return connections[i];
}
}
// 若没有空闲连接, 当前线程进入等待
synchronized (this) {
try {
log.debug("Waitting...");
this.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
// 6. 归还连接
public void free(Connection connection) {
for (int i = 0; i < poolSize; i++) {
if (connections[i] == connection) {
states.set(i, 0);
synchronized (this) {
log.debug("Free: {}", connection);
this.notifyAll();
}
break;
}
}
}
}
class MockConnection implements Connection{
private String name;
public MockConnection(String name) {
this.name = name;
}
@Override
public String toString() {
return "MockConnection{" +
"name='" + name + '\'' +
'}';
}
/**
* 省略实现方法。。。。
*/
}
还未实现功能:
- 连接动态增长与收缩
- 连接保活(心跳机制,Keep-Alive)
- 等待超时处理
- 分布式 Hash
8.5 final 原理
final 变量赋值会通过 putfield 指令完成,这条指令之后会加入写屏障,保证在其他线程读到它的值时不会出现为 0 的情况:
如果类的属性加了 final 会直接在线程的栈中当作一个局部变量使用,如果不加则放在共享区域用于访问。