单例模式(Singleton Pattern)是一种设计模式,目的是确保一个类只有一个实例,并提供一个全局访问点。 这种模式属于创建型模式,主要用于当某个类在系统中只能存在一个实例时,例如配置管理器、数据库连接池等场景。
单例模式的动机在于确保某些类在系统中只有一个实例。例如,一个系统中只能有一个打印任务管理器、窗口管理器或文件系统等。如果不使用机制对对象进行唯一化,可能会导致多个实例被创建,浪费资源,甚至引起状态不一致的问题。
在Java中,单例模式(Singleton Pattern)是一种常用的设计模式,用于确保一个类只有一个实例,并提供一个全局访问点。
这种方式在类加载时就创建了单例实例,因此它保证了线程安全,并且在类加载时就完成了实例的创建,适用于单例实例需要尽早创建并且不需要延迟加载的场景。
如果用洗碗来做类比的话,吃完饭立刻就去洗碗这个就是属于“饿汉模式”的范畴
饿汉式单例模式主要通过将类的实例初始化放在静态变量中来实现,由于静态变量在类加载时就会初始化,因此这种方式可以保证实例的唯一性。
静态成员变量方式
在这种方式中,直接在类中声明一个私有的静态成员变量,并在声明时直接实例化。同时,提供一个公共的静态方法来返回这个实例。构造方法需要被私有化,以防止外部通过 new 关键字创建实例。
示例代码:
public class CommonUtil {
// 私有静态成员变量,类加载时初始化
private static final CommonUtil instance = new CommonUtil();
// 私有构造方法,防止外部通过new创建实例
private CommonUtil() {}
// 公共静态方法,返回唯一实例
public static CommonUtil getInstance() {
return instance;
}
public void doSomething() {
// 操作实例的代码
}
}
静态代码块方式
虽然静态成员变量方式更为常见,但也可以使用静态代码块来实现相同的效果。在静态代码块中对静态成员变量进行初始化。这种方式在写法上略有不同,但本质上都是在 类加载时完成实例的初始化。
示例代码:
public class CommonUtil {
// 私有静态成员变量
private static final CommonUtil instance;
static {
// 静态代码块中初始化实例
instance = new CommonUtil();
}
// 私有构造方法,防止外部通过new创建实例
private CommonUtil() {}
// 公共静态方法,返回唯一实例
public static CommonUtil getInstance() {
return instance;
}
public void doSomething() {
// 操作实例的代码
}
}
优点:
缺点:
java.io.Serializable
接口并添加一个读对象的方法来解决,但这增加了实现的复杂性。这种方式在第一次被调用时才创建单例实例,可以延迟资源的消耗,但是如果多个线程同时调用getInstance()方法,可能会创建多个实例,因此不是线程安全的。
默认实现方式-线程不安全
示例代码:
public class CommonUtil {
// 声明私有静态变量,初始化为null
private static CommonUtil instance;
// 私有构造方法,防止外部通过new创建实例
private CommonUtil() {}
// 提供公共静态方法,返回类的唯一实例
public static CommonUtil getInstance() {
// 检查实例是否已创建
if (instance == null) {
// 创建实例
instance = new CommonUtil();
}
// 返回实例
return instance;
}
public void doSomething() {
// 操作实例的代码
}
}
线程安全
通过synchronized
关键字确保线程安全。但是,每次调用getInstance()方法时都会进行同步,这可能会导致性能问题。
示例代码:
public class CommonUtil {
// 声明私有静态变量,初始化为null
private static CommonUtil instance;
// 私有构造方法,防止外部通过new创建实例
private CommonUtil() {}
// 提供公共静态方法,返回类的唯一实例
public static CommonUtil getInstance() {
// 检查实例是否已创建
if (instance == null) {
synchronized (CommonUtil.class) {
// 创建实例
instance = new CommonUtil();
}
}
// 返回实例
return instance;
}
public void doSomething() {
// 操作实例的代码
}
}
线程安全的优化
双重检查锁定(Double-CheckedLocking)**:通过两次检查实例是否已创建,并在第二次检查时使用 **synchronized
关键字加锁,以减少同步的开销。同时,为了保证跨线程的内存可见性,实例变量需要使用 volatile
关键字修饰。
这种方式是懒汉式的一个变种,它只在实例为null时才进行同步,减少了获取实例时的同步开销,既保证了线程安全,又提高了性能。
示例代码:
public class CommonUtil {
private static volatile CommonUtil instance;
private CommonUtil() {}
public static CommonUtil getInstance() {
if (instance == null) {
synchronized (CommonUtil.class) {
if (instance == null) {
instance = new CommonUtil();
}
}
}
return instance;
}
public void doSomething() {
// 操作实例的代码
}
}
这种方式是通过枚举来实现单例,它不仅能避免多线程问题,还能防止反序列化重新创建新的对象。枚举类型的每个元素都是静态的,因此不需要显式的实例化。、
在枚举方式中,CommonUtil.INSTANCE
就是单例实例,你可以通过CommonUtil.INSTANCE.doSomething()
来访问单例的方法。这种方式简单且线程安全,由JVM从根本上提供保障,是实现单例模式的最佳方法之一。
示例代码:
public enum CommonUtil {
INSTANCE;
public void doSomething() {
// 操作实例的代码
}
}
单例模式是一种确保特定类只有一个实例,并提供全局访问点的设计模式。它适用于配置管理器、数据库连接池等需要唯一实例的场景。单例模式的实现方式主要有饿汉式、懒汉式和枚举式。
饿汉式在类加载时就创建实例,简单且线程安全,但可能导致资源浪费和内存占用。懒汉式则在第一次调用时创建实例,可以延迟资源消耗,但需要额外的同步措施来保证线程安全,如双重检查锁定。枚举式单例利用Java枚举的特性,实现简单且天然线程安全,还能防止反序列化创建新实例的问题,是实现单例的最佳实践。
每种实现方式都有其适用场景和优缺点。饿汉式适合实例创建成本低、使用频繁的场景;懒汉式适合实例创建成本高、使用频率低的场景;枚举式则适合所有需要单例的场景,特别是需要保证绝对线程安全的情况。选择合适的单例实现方式,可以有效地提高资源利用率和程序性能,同时保证程序的正确性。
欢迎关注公众号:“全栈开发指南针”
这里是技术潮流的风向标,也是你代码旅程的导航仪!
Let’s code and have fun!