1. 不可变对象
@Slf4j(topic = "c.Test1")
public class Test1 {
public static void main(String[] args) {
SimpleDateFormat sdf=new SimpleDateFormat("yyyy-MM-dd");
for (int i = 0; i < 100; i++) {
new Thread(()->{
try {
log.debug("{}",sdf.parse("1951-04-21"));
} catch (ParseException e) {
e.printStackTrace();
}
}).start();
}
}
}
可见线程不安全,加锁可解决但性能太低。换成不可变对象就没问题了。如DateTimeFormatter
package cn.itcast.n7copy;
import lombok.extern.slf4j.Slf4j;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.time.format.DateTimeFormatter;
/**
* ClassName: Test1
* Package: cn.itcast.n7copy
* Description:
*
* @Author: 1043
* @Create: 2024/9/8 - 20:15
* @Version: v1.0
*/
@Slf4j(topic = "c.Test1")
public class Test1 {
public static void main(String[] args) {
DateTimeFormatter stf = DateTimeFormatter.ofPattern("yyyy-MM-dd");
for (int i = 0; i < 100; i++) {
new Thread(() -> {
log.debug("{}", stf.parse("1951-04-21"));
}).start();
}
}
private static void test() {
SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd");
for (int i = 0; i < 100; i++) {
new Thread(() -> {
synchronized (sdf) {
try {
log.debug("{}", sdf.parse("1951-04-21"));
} catch (ParseException e) {
e.printStackTrace();
}
}
}).start();
}
}
}
这次运行没问题了。
2. 不可变设计
final class String
implements java.io.Serializable, Comparable<String>, CharSequence {
/**
* The value is used for character storage.
*/
private final char value[];
/**
* Cache the hash code for the string
*/
private int hash; // Default to 0
// ...
}
final 的使用
可见该类、类中所有属性都是 final 的,属性用 final 修饰保证了该属性是只读的,不能修改
类用 final 修饰保证了该类中的方法不能被覆盖,防止子类无意间破坏不可变性。
保护性拷贝
使用字符串时,也有一些跟修改相关的方法,比如 substring 等,那么下面就看一看这些方法是如何实现的,就以 substring 为例:
public String substring(int beginIndex) {
if (beginIndex < 0) {
throw new StringIndexOutOfBoundsException(beginIndex);
}
int subLen = value.length - beginIndex;
if (subLen < 0) {
throw new StringIndexOutOfBoundsException(subLen);
}
return (beginIndex == 0) ? this : new String(value, beginIndex, subLen);
}
发现其内部是调用 String 的构造方法创建了一个新字符串,再进入这个构造看看,是否对 final char[] value 做出了修改:
public String(char value[], int offset, int count) {
if (offset < 0) {
throw new StringIndexOutOfBoundsException(offset);
}
if (count <= 0) {
if (count < 0) {
throw new StringIndexOutOfBoundsException(count);
}
if (offset <= value.length) {
this.value = "".value;
return;
}
}
// Note: offset or count might be near -1>>>1.
if (offset > value.length - count) {
throw new StringIndexOutOfBoundsException(offset + count);
}
this.value = Arrays.copyOfRange(value, offset, offset+count);
}
结果发现也没有,构造新字符串对象时,会生成新的 char[] value,对内容进行复制 。这种通过创建副本对象来避免共享的手段称之为【保护性拷贝(defensive copy)】
3. 享元模式
1. 简介
定义 英文名称:Flyweight pattern. 当需要重用数量有限的同一类对象时
wikipedia: A flyweight is an object that minimizes memory usage by sharing as much data aspossible with other similar objects
出自 "Gang of Four" design patterns
归类 Structual patterns
2. 体现
2.1 包装类
在JDK中 Boolean,Byte,Short,Integer,Long,Character 等包装类提供了 valueOf 方法,例如 Long 的valueOf 会缓存 -128~127 之间的 Long 对象,在这个范围之间会重用对象,大于这个范围,才会新建 Long 对象:
2.2 String 串池
2.3 BigDecimal BigInteger (第六章取款的案例BigDecimal 不能保证线程安全是因为取款涉及操作之间的组合,BigDecimal 只能保证单个操作时线程安全,不能保证多个对象的组合也是线程安全,所以需要原子引用保证)。
3.DIY
设计一个简单的数据库连接池
public class Test3 {
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 {
Thread.sleep(new Random().nextInt(1000));
} catch (InterruptedException e) {
e.printStackTrace();
}
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(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 {
//太多了,这就省略了
}
先打印wait可能是因为log是比较慢的,这是已经有线程取过连接了。
以上实现没有考虑:
连接的动态增长与收缩
连接保活(可用性检测)
等待超时处理
分布式 hash
对于关系型数据库,有比较成熟的连接池实现,例如c3p0, druid等 对于更通用的对象池,可以考虑使用apache,commons pool,例如redis连接池可以参考jedis中关于连接池的实现。