what 是单例
保证一个类只允许创建一个对象 ,这个类就是单例类,这中设计模式就叫单例模式。
how 定义
- 构造方法私有,避免外部访问
- 创建对象的安全问题考虑
- 是否支持延迟加载
- 考虑 getinstance的性能是否合适
why 需要它
- 处理资源访问冲突
比如一个打印日志的例子,2个人都new一个日志对象,同时打印一个问题,如果超过一个对象,行为不正确
public class Logger {
private FileWriter writer;
public Logger() {
File file = new File("/Users/wangzheng/log.txt");
writer = new FileWriter(file, true); //true表示追加写入
}
//这里同时调用,会导致日志错乱,可能存在日志信息互相覆盖的情况。
public void log(String message) {
writer.write(message);
}
}
// Logger类的应用示例:
public class UserController {
private Logger logger = new Logger();
public void login(String username, String password) {
// ...省略业务逻辑代码...
logger.log(username + " logined!");
}
}
public class OrderController {
private Logger logger = new Logger();
public void create(OrderVo order) {
// ...省略业务逻辑代码...
logger.log("Created an order: " + order.toString());
}
}
- 表示全局唯一
比如,项目中配置文件,一个项目只需要有一份就可以了,不需要每次用到还有创建它。或者 ,唯一递增 ID 号码生成 ,如果有2个对象,还存在重复生成id情况 、或者数据库连接池
import java.util.concurrent.atomic.AtomicLong;
public class IdGenerator {
// AtomicLong是一个Java并发库中提供的一个原子变量类型,
// 它将一些线程不安全需要加锁的复合操作封装为了线程安全的原子操作,
// 比如下面会用到的incrementAndGet().
private AtomicLong id = new AtomicLong(0);
private static final IdGenerator instance = new IdGenerator();
private IdGenerator() {}
public static IdGenerator getInstance() {
return instance;
}
public long getId() {
return id.incrementAndGet();
}
}
// IdGenerator使用举例
long id = IdGenerator.getInstance().getId();
how 实现
一、懒汉式
饿汉式的实现方式,在类加载的期间,就已经将 instance 静态实例初始化好了,所以,instance 实例的创建是线程安全的。不过,这样的实现方式不支持延迟加载实例。
对于一些创建对象比较耗时,建议使用懒汉式,在程序启动时尽早创建对象,也可以暴露问题
public class IdGenerator {
private AtomicLong id = new AtomicLong(0);
private static final IdGenerator instance = new IdGenerator();
private IdGenerator() {}
public static IdGenerator getInstance() {
return instance;
}
public long getId() {
return id.incrementAndGet();
}
}
二、饿汉式
- 懒汉式相对于饿汉式的优势是支持延迟加载。这种实现方式会导致频繁加锁、释放锁,以及并发度低等问题,频繁的调用会产生性能瓶颈。
- 双重检测实现方式既支持延迟加载、又支持高并发的单例实现方式。只要 instance 被创建之后,再调用 getInstance() 函数都不会进入到加锁逻辑中。所以,这种实现方式解决了懒汉式并发度低的问题。
public class IdGenerator {
private AtomicLong id = new AtomicLong(0);
private static IdGenerator instance;
private IdGenerator() {}
public static synchronized IdGenerator getInstance() {
if (instance == null) {
instance = new IdGenerator();
}
return instance;
}
public long getId() {
return id.incrementAndGet();
}
}
// 修改为双重检测,解决性能问题
public class IdGenerator {
private AtomicLong id = new AtomicLong(0);
private static IdGenerator instance;
private IdGenerator() {}
public static IdGenerator getInstance() {
if (instance == null) {
synchronized(IdGenerator.class) { // 此处为类级别的锁
if (instance == null) {
instance = new IdGenerator();
}
}
}
return instance;
}
public long getId() {
return id.incrementAndGet();
}
}
// instance 成员变量添加 volatile 关键字来禁止指令重排序即
三、静态方法
内部SingletonHolder 在使用到的时候,才会进行创建,jvm保证线程安全
public class IdGenerator {
private AtomicLong id = new AtomicLong(0);
private IdGenerator() {}
private static class SingletonHolder{
private static final IdGenerator instance = new IdGenerator();
}
public static IdGenerator getInstance() {
return SingletonHolder.instance;
}
public long getId() {
return id.incrementAndGet();
}
}
四、枚举
这种方式最简洁,可以保证不能通过反射实例化,枚举模式的单例还可以防止序列化和反序列化生成新的实例
public enum IdGenerator {
INSTANCE;
private AtomicLong id = new AtomicLong(0);
public long getId() {
return id.incrementAndGet();
}
}
why 有时候不使用单例类会不会更好
一、对单元测试不是很友好
当我们需要改变是实现的时候
public class Order {
public void create(...) {
//...
long id = IdGenerator.getInstance().getId();
// 需要将上面一行代码,替换为下面一行代码
long id = OrderIdGenerator.getIntance().getId();
//...
}
}
public class User {
public void create(...) {
// ...
long id = IdGenerator.getInstance().getId();
// 需要将上面一行代码,替换为下面一行代码
long id = UserIdGenerator.getIntance().getId();
}
}
二、隐藏类和类之间的依赖关系
三、对扩展性不好
怎么替换单例模式
- 使用工厂模式
- ioc容器注入