单例模式
一、核心作用:
保证一个类全局只有一个实例,并且提供了一个该实例的全局访问点。
二、单例模式的优点
减小系统开销等,优化共享资源的访问。
三、常见的五种单例实现方式
- 饿汉式(线程安全,调用效率高,但是不能延时加载);
- 懒汉式(线程安全,调用效率不高。但是可以延时加载);
- 双重检测锁式(由于jvm底层内部模型原因,偶尔会出现问题);
- 静态内部类式(线程安全,调用效率高。可以延时加载);
- 枚举单例(线程安全,调用效率高,但不能延时加载);
四、具体实现
1、饿汉式
public class Singleton {
//类初始化时,立即加载这个对象(没有延时加载的优势)。加载类时,天然的是线程安全的!
private static Singleton instance = new Singleton();
//私有化构造器
private Singleton(){
}
//方法没有同步,调用效率高!
public static Singleton getInstance(){
return instance;
}
}
注:饿汉式不管后期会不会用到该类的实例,都会创建出来,如果后期没有用到会造成资源的浪费。
2、懒汉式
public class Singleton {
//类初始化时,不初始化这个对象(延时加载,真正用的时候再创建)。
private static Singleton instance;
private Singleton(){ //私有化构造器
}
//方法同步,调用效率低!
public static synchronized Singleton getInstance(){
if(instance==null){
instance = new Singleton();
}
return instance;
}
}
3、双重检测锁模式
public class Singleton {
private static Singleton instance = null;
public static Singleton getInstance() {
if (instance == null) {
Singleton sc;
synchronized (Singleton.class) { //双重检测锁,如果是单层,容易发生并发问题
sc = instance;
if (sc == null) {
synchronized (Singleton.class) {
if(sc == null) {
sc = new Singleton();
}
}
instance = sc;
}
}
}
return instance;
}
//私有化构造器
private Singleton() {
}
}
注:虽然这种方式可以实现延时加载,但是由于jvm底层内部模型原因和编译器优化(指令重排)原因,不建议使用该方式。
4、嵌套类(静态内部类)方式
public class Singleton {
private static class SingletonClassInstance {
private static final Singleton instance = new Singleton();
}
private Singleton(){
}
//方法没有同步,调用效率高!
public static Singleton getInstance(){
return SingletonClassInstance.instance;
}
}
这里原来笔者写的是静态内部类,容易和内部类搞混,进入了误区,更正一下:
定义在一个类内部的类,这个类叫做“嵌套类”。嵌套类又分为两种:static和非static。后者又被称为“内部类”。
嵌套类是其所在类的成员,static嵌套类不能访问所在类的非static的成员变量和方法,而内部类可以访问所在类的所有成员,即使成员被private修饰。
static嵌套类:因为其不能直接访问所在类的非static成员变量和方法,所有其必须通过绑定所在类的实例来进行访问。
内部类:可以访问所有的成员,内部类一般分为成员内部类、局部内部类、匿名内部类。
- 局部内部类:定义在方法内部的类。它的作用域仅局限于方法内,和局部变量一样不能被修饰为private、public、protected、static,并且只能访问方法内部定义的final变量。
- 匿名内部类:匿名内部类与局部类对于作用域内的变量享有相同的访问权限。具体可以参考:(后续放链接)
扯远了
5、枚举类
public enum Singleton {
//这个枚举元素,本身就是单例对象
INSTANCE;
//添加自定义的操作
public void singletonOperation(){
}
}
五、值得注意的地方
以上除了枚举类的实现,其他的方式都能被反射或者序列化反序列化破解单例模式。
防止反射破解的方式有在类的构造函数中加入判断,如果当前类有实例就跳过或者报错(根据具体的业务需求);
防止序列化反序列化破解方式有在类中加入一下代码
//在反序列化时,如果定义了readResolve()则直接返回此方法指定的对象。而不需要单独再创建新对象!
private Object readResolve() throws ObjectStreamException {
return instance;
}
六、常用的场景
- 在项目中读取配置的类一般只有一个对象。
- 数据库的连接池设计一般也采用单例模式。
- 应用程序中的日志应用,一般也采用单例模式,一个实例去操作日志文件的读写等
…