这是我参与8月更文挑战的第9天,活动详情查看:8月更文挑战
单例模式
指一个类只有一个实例,且该类能自行创建这个实例的一种模式
特点
- 单例类只有一个实例对象;
- 该单例对象必须由单例类自行创建;
- 单例类对外提供一个访问该单例的全局访问点。
优点
- 单例模式可以保证内存里只有一个实例,减少了内存的开销。
- 避免频繁的创建销毁对象,可以提高性能;
- 可以避免对资源的多重占用。
- 单例模式设置全局访问点,可以优化和共享资源的访问。
缺点
- 单例模式一般没有接口,扩展困难。如果要扩展,则除了修改原来的代码,没有第二种途径,违背开闭原则。
- 在并发测试中,单例模式不利于代码调试。在调试过程中,如果单例中的代码没有执行完,也不能模拟生成一个新的对象。
- 单例模式的功能代码通常写在一个类中,如果功能设计不合理,则很容易违背单一职责原则。
使用场景:
- 要求生产唯一序列号。
- WEB 中的计数器,不用每次刷新都在数据库里加一次,用单例先缓存起来。
- 创建的一个对象需要消耗的资源过多,比如 I/O 与数据库的连接等。
手撸代码
1、饿汉式
在加载的时候就加载了,不管是什么时候用到,这样保证了线程的安全,但是如果长时间没用到则浪费了内存空间
public class Singleton {
private static Singleton singleton = new Singleton();
private Singleton() {}
public static Singleton get() {
return singleton;
}
}
2、懒汉式
懒汉式单例是延迟加载,只有需要的时候才会实例化对象再使用
// 懒汉式
public class Singleton {
private static Singleton singleton;
private Singleton() {}
public static Singleton get() {
if (singleton == null) {
singleton = new Singleton();
}
return singleton;
}
}
这种写法在多线程环境是线程不安全的,当一个线程通过if (singleton1 == null)时,另一个线程也同时通过了这个判断,这样就产生了多个实例。
3、双重校验锁
属于懒汉式的一种,在懒汉式的基础上加了synchronized关键字
// 双重校验锁
public class Singleton {
private static Singleton singleton;
private Singleton() {}
public static Singleton get() {
if (singleton == null) {
synchronized(Singleton.class) {
if (singleton == null) {
singleton = new Singleton();
}
}
}
return singleton;
}
}
这样先判断一次,再加锁进行第二次判断,是线程安全的;
优点:线程安全;延迟加载;效率较高。
4、内部静态类
public class Singleton {
private static class SingletonHolder {
private static Singleton singleton = new Singleton();
}
private Singleton() {}
public static Singleton get() {
return SingletonHolder.singleton;
}
}
在项目启动初始化时,虚拟机只加载了Singleton类,内部的SingletonHolder并没有加载,只有第一次加载get方法时,虚拟机才加载SingletonHolder并初始化singleton;保证了对象的唯一性。
5、枚举
public enum Singleton {
INSTANCE;
public void doSomeThing(){
}
}
这种实现方式还没有被广泛采用,但这是实现单例模式的最佳方法。 它更简洁,自动支持序列化机制,绝对防止多次实例化 (如果单例类实现了Serializable接口,默认情况下每次反序列化总会创建一个新的实例对象
通过枚举类,他能自动避免序列化/反序列化攻击,以及反射攻击(枚举类不能通过反射生成)。