单例模式是一种常用的设计模式,它确保一个类只有一个实例,并提供一个全局访问点来访问这个实例。常用的几种单例模式包括饿汉式、懒汉式、双重检验锁和静态内部类。
- 饿汉式单例模式
饿汉式单例模式是在类加载时就创建了实例,因此在多线程环境下也是安全的。示例代码如下:
public class Singleton {
private static Singleton instance = new Singleton();
private Singleton() {}
public static Singleton getInstance() {
return instance;
}
}
- 懒汉式单例模式
懒汉式单例模式是在第一次使用时才创建实例,因此可能会存在线程安全问题。为了解决这个问题,可以使用双重检验锁或者静态内部类的方式。示例代码如下:
// 双重检验锁
public class Singleton {
private static volatile Singleton instance;
private Singleton() {}
public static Singleton getInstance() {
if (instance == null) {
synchronized (Singleton.class) {
if (instance == null) {
instance = new Singleton();
}
}
}
return instance;
}
}
// 静态内部类
public class Singleton {
private Singleton() {}
private static class SingletonHolder {
private static final Singleton INSTANCE = new Singleton();
}
public static Singleton getInstance() {
return SingletonHolder.INSTANCE;
}
}
- 枚举单例模式
枚举单例模式是在Java 5中新增的一种单例模式,它是线程安全的,并且可以防止反射和序列化等攻击。示例代码如下:
public enum Singleton {
INSTANCE;
public void doSomething() {
// do something
}
}
在这些单例模式中,实际开发中,最常用的单例模式是饿汉式单例和懒汉式双重检查锁单例。
饿汉式单例的优点是实现简单,线程安全,且在使用时没有额外的同步开销,因此在需要在多个线程中频繁调用单例对象的情况下,是一个不错的选择。但是其缺点是会在类加载时就创建对象,占用内存,可能会影响程序的性能和资源利用。
懒汉式双重检查锁单例则通过延迟初始化的方式来实现懒加载,只有在使用时才会创建单例对象,因此不会占用过多的内存资源。同时又通过双重检查锁的方式保证了线程安全性。但是其实现比较复杂,需要注意线程安全的细节,因此在实现时需要更加小心谨慎。
因此,选择哪种单例模式取决于具体的场景和需求。如果在需要在多个线程中频繁调用单例对象的情况下,可以考虑使用饿汉式单例;如果需要延迟初始化并且要保证线程安全,可以考虑使用懒汉式双重检查锁单例。
枚举的单例模式天生就是线程安全的,因为:
-
枚举类型在JVM中只会被实例化一次,因此在多线程环境下也是线程安全的。
-
枚举类型在Java语言中有特殊的处理机制:枚举类型的实例在类加载时就会被创建,因此在多线程环境下也是线程安全的。
底层的实现方式是,在Java编译器编译枚举类型时,会自动将其转换为一个继承自Enum类的final类。而枚举类型的成员在类加载时就被实例化,且实例化后无法被修改,因此枚举类型本身就具有线程安全性。
下面是一个简单的枚举类型的示例代码:
public enum Season {
SPRING,
SUMMER,
AUTUMN,
WINTER;
}
经过编译器编译后,这个枚举类型的底层代码类似于下面的代码:
final class Season extends Enum<Season> {
public static final Season SPRING = new Season();
public static final Season SUMMER = new Season();
public static final Season AUTUMN = new Season();
public static final Season WINTER = new Season();
private Season() {
super(name(), ordinal());
}
public static Season[] values() {
return (Season[]) $VALUES.clone();
}
public static Season valueOf(String name) {
return (Season) Enum.valueOf(Season.class, name);
}
private static final Season[] $VALUES = { SPRING, SUMMER, AUTUMN, WINTER };
}
在这个底层代码中,Season继承自Enum类,并包含了4个常量:SPRING、SUMMER、AUTUMN、WINTER。这些常量在类加载时被实例化,因此它们的实例在JVM中只会被实例化一次,从而保证了枚举类型的线程安全性。
由于枚举类型的特殊处理机制,枚举单例模式在Java中被广泛使用,它不仅具有线程安全性,还可以防止反射和序列化等攻击。
常见的开发框架中也会有不少地方有单例模式的出现。
- Spring 框架中,Bean 对象默认为单例模式,每个 Bean 只会创建一次,以后每次使用都会返回同一个对象。例如:
@Service
public class MyService {
// 单例模式
}
MyBatis 框架中,SqlSessionFactory 对象是单例模式,它负责创建 SqlSession 对象。例如:
public class MybatisConfig {
@Bean
public SqlSessionFactoryBean sqlSessionFactoryBean() {
SqlSessionFactoryBean sqlSessionFactoryBean = new SqlSessionFactoryBean();
// ...
return sqlSessionFactoryBean;
}
}
Log4j 日志框架中,LogManager 对象是单例模式,它管理所有的 Logger 对象。例如:
public class LogManager {
private static final Logger LOGGER = Logger.getLogger(LogManager.class);
// ...
}
JDK 中,Runtime 类是单 例模式,它表示应用程序的运行时环境。例如:
Runtime runtime = Runtime.getRuntime();
总之,单例模式在各种框架和库中广泛使用,它可以确保一个类只有一个实例,并且提供了一种方便的全局访问方式,从而提高了程序的性能和可维护性。