概述
Java 单例模式是一种创建对象的设计模式。单例模式确保一个类只有一个实例被创建,并提供一个全局访问点来访问该实例。这种模式是一种创建型模式,它涉及一个单一的类,该类负责创建自己的一个对象,同时确保只能创建一个对象。
实现方式
饿汉式
public class Singleton {
/**
* 私有化构造器,外部不能进行实例化
**/
private Singleton() {}
/**
* 类加载时就进行实例化
* private 外部不能进行访问&修改
* final 保证属性不会被修改,final特征的变量在编译时就绑定了值,而不是在运行时,提高了程序的性能
* 静态变量在内存中只有一个拷贝,JVM只为静态变量分配一次内存,保证了线程安全
*/
private final static Singleton SINGLETON = new Singleton();
/**
* 提供一个公有的方法,返回实例对象
*/
public static Singleton getInstance() {
return SINGLETON;
}
}
优点:
- 线程安全:在类加载时就进行实例化,只有一个实例对象存在,因此不存在线程安全性问题;
- 实现简单:实现简单,代码实现也比较简单;
- 没有锁机制,执行效率高。
缺点:
- 没有懒加载:由于是在类加载时就进行实例化,所以可能会导致内存浪费,无法满足懒加载的需求;
- 无法通过继承来进行扩展:由于构造器是私有的,不能通过继承来生成子类的实例;
- 可读性差:在修饰static变量时需要同时使用final和static修饰,并且是在变量声明时进行初始化,这会降低代码的可读性。
饿汉式单例模式适用于只需要一个实例对象,而且实例对象在程序运行期间不需要更改的情况下,是一种简单高效的实现方式。但是如果需要更高的灵活性和扩展性,建议使用懒汉式单例模式等其他单例模式来实现。
懒汉式
- 线程不安全
public class Singleton {
/**
* 私有化构造器,外部不能进行实例化
**/
private Singleton() {}
/**
* 声明一个静态的实例
**/
private static Singleton singleton;
/**
* 提供一个全局的静态方法
* 用到的时候才去实例化
* 在多线程的情况下,可能会出现多个实例
*/
public static Singleton getInstance() {
if (null == singleton) {
singleton = new Singleton();
}
return singleton;
}
}
- 线程安全
/**
* @author yiwenjie
* 懒汉式单例
*/
public class Singleton {
/**
* 私有化构造器,外部不能进行实例化
**/
private Singleton() {}
/**
* 声明一个静态的实例
**/
private static Singleton singleton;
/**
* 提供一个全局的静态方法
* 用到的时候才去实例化
* synchronized 保证线程安全
*/
public static synchronized Singleton getInstance() {
if (null == singleton) {
singleton = new Singleton();
}
return singleton;
}
}
- 优点:
-
节省了系统资源。只有当需要使用单例对象时才进行实例化,避免了一开始就占用内存资源的问题。
-
具有延迟加载的特性,即按需加载,提高了程序的性能和响应速度。
- 缺点:
- 在多线程的情况下,如果没有进行同步处理,可能会出现多个线程同时判断singleton为null的情况,从而导致实例化多个对象的问题。
- 存在并发风险。需要特别注意,如果实例化单例的代码存在并发风险时,如多个线程同时执行getInstance()方法,可能会导致多个实例被创建。
- synchronized关键字的影响。如果使用synchronized关键字进行同步处理,会影响getInstance()方法的性能,降低程序的执行效率
双重检查
/**
* @author yiwenjie
* 懒汉式单例
*/
public class Singleton {
/**
* 私有化构造器,外部不能进行实例化
**/
private Singleton() {}
/**
* 声明一个静态的实例
* volatile关键字保证了变量的可见性,防止指令重排序
**/
private static volatile Singleton singleton;
/**
* 提供一个全局的访问点
* 先判断是否为空,为空则加锁,再判断是否为空,为空则创建实例
*/
public static Singleton getInstance() {
if (null == singleton) {
synchronized (Singleton.class) {
if (null == singleton) {
singleton = new Singleton();
}
}
}
return singleton;
}
}
优点:
- 线程安全:通过双重校验锁,保证了线程安全。
- 懒加载:只有在第一次调用getInstance方法时才会创建对象,节省了内存。
- 高效性:相对于单重校验锁的方式,使用双重校验锁的方式,在保证线程安全的情况下,可以提高效率,在多线程环境下性能得到大大提升。
缺点:
- 可能会引发空指针异常:如果在某个线程创建对象时,其他线程未执行第一个if判断而直接返回了singleton的值,此时将会引发NullPointerException异常。
- 可读性差:由于双重校验锁的代码比较复杂,可读性比较差,可读性是一种代码的质量,可读性差的代码维护难度大。
总之,双重校验锁单例模式在多线程高并发环境下比较常用,但需要注意线程安全和空指针异常问题。在单线程或者低并发环境下,可以使用饿汉式单例模式,代码简单,可读性好。
静态内部类
public class Singleton {
/**
* 私有化构造器,外部不能进行实例化
**/
private Singleton() {}
public static class SingletonInstance {
private static final Singleton INSTANCE = new Singleton();
}
public static Singleton getInstance() {
return SingletonInstance.INSTANCE;
}
}
优点:
- 线程安全:由于JVM在加载静态内部类的时候进行了同步,所以保证了线程安全。
- 懒加载:只有在第一次调用getInstance方法时才会创建对象,节省了内存。
- 高效性:相对于双重校验锁的方式,使用静态内部类的方式,在保证线程安全的情况下,可以提高效率,在多线程环境下性能得到大大提升。
- 线程安全和懒加载的最佳实践:该方式是线程安全和懒加载的最佳实践,代码简单易懂,可读性好。
缺点:
- 可能会引发反序列化漏洞:如果该类被反序列化到内存中,反序列化时会重新创建新的实例,破坏单例模式。
- 不支持传参:该实现方式没有办法支持传参。 总的来说,静态内部类实现单例模式是一种优秀的选择,具有懒加载和线程安全的特性,可以保证单例模式的正确性和可读性。但是需要注意对象可能会被序列化,因此可以考虑增加readResolve方法防止反序列化攻击。
枚举
public enum Singleton {
SINGLETON;
public void add() {
System.out.println("add...");
}
}
注意事项
当想实例化一个单例类的时候,必须要记住使用相应的获得对象的方法,而不是使用new
使用场景
需要频繁的进行创建和销毁的对象、创建对象时耗时过多或耗费资源过多(即:重量级对象),但又经常用到的对象、工具类对象、频繁访问数据库或文件的对象(比如数据源、session工厂等)
标签:Singleton,singleton,模式,实例,线程,单例,static,设计模式 From: https://www.cnblogs.com/ywjcqq/p/17391673.html