单例模式详解(DLC和spring源码中的应用)
单例模式通过确保一个类只有一个实例并提供一个全局访问点来访问该实例来实现。
通常,实现单例模式有两种方法:饿汉式和懒汉式。
饿汉式单例在类加载时就创建了实例,所以不存在线程安全问题。懒汉式单例在第一次调用时才创建实例,并且必须通过同步机制来保证线程安全。
下面是一个简单的饿汉式单例类的示例:
public class Singleton {
// 创建 Singleton 的一个对象
private static Singleton instance = new Singleton();
// 让构造函数为 private,这样该类就不会被实例化
private Singleton() {}
// 获取唯一可用的对象
public static Singleton getInstance() {
return instance;
}
// 显示消息
public void showMessage() {
System.out.println("Hello World!");
}
}
下面是一个简单的懒汉式单例类的示例:
public class Singleton {
// 创建 Singleton 的一个对象
private static Singleton instance;
// 让构造函数为 private,这样该类就不会被实例化
private Singleton() {}
// 获取唯一可用的对象
public static synchronized Singleton getInstance() {
if (instance == null) {
instance = new Singleton();
}
return instance;
}
// 显示消息
public void showMessage() {
System.out.println("Hello World!");
}
}
改进之线程安全-DLC
存在这样一种情况,当懒汉模式中进程A运行到if(instance==null)时判断完还未实例化,这是时间片运行完或者忽然CPU调用线程B,再线程B中创造了实例。现在已经存在了一个实例,可是再回到线程A中的时候,线程A的if判断仍是实例未生成,这时就出现问题了。
DLC是指Double-checked Locking,即双重检查锁定。它是一种用于避免多线程同步问题的优化方法。
懒汉式单例在第一次调用时才创建实例,并且必须通过同步机制来保证线程安全。这可能会导致性能问题,因为每次调用 getInstance() 方法都需要同步,这会使系统性能降低。
DLC可以解决这个问题,它通过使用两个if语句来减少同步的使用。首先,我们检查实例是否已经存在,如果不存在,再次通过同步来检查实例是否已经存在,如果不存在,就创建实例。
然而DLC在Java 1.4及以前版本中是有效的,因为Java 1.4及以前版本的内存模型不保证多线程情况下的数据可见性。在Java 5.0中,内存模型已经改进,并且保证了多线程情况下的数据可见性。但是,DLC仍然无法保证多线程情况下的正确性,因此不推荐使用。
为了解决DLC的问题,Java 5.0引入了一种新的机制,称为内部锁(也称为监视器锁)。内部锁可以保证多线程情况下的正确性,因此是实现单例模式的更好方法。
下面是一个使用DLC的懒汉式单例类的示例:
public class Singleton {
// 创建 Singleton 的一个对象
private static Singleton instance;
// 让构造函数为 private,这样该类就不会被实例化
private Singleton() {}
// 获取唯一可用的对象
public static Singleton getInstance() {
if (instance == null) {
synchronized (Singleton.class) { //内部锁
if (instance == null) {
instance = new Singleton();
}
}
}
return instance;
}
// 显示消息
public void showMessage() {
System.out.println("Hello World!");
}
}
DLC的优点是在第一次调用 getInstance() 方法时才需要同步,这样就可以提高性能。
或者我们也可以使用ReentrantLock来给线程加锁,
public class Singleton {
// 创建 Singleton 的一个对象
private static Singleton instance;
// 创建一个可重入锁
private static ReentrantLock lock = new ReentrantLock();
// 让构造函数为 private,这样该类就不会被实例化
private Singleton() {}
// 获取唯一可用的对象
public static Singleton getInstance() {
// 加锁
lock.lock();
try {
if (instance == null) {
instance = new Singleton();
}
return instance;
} finally {
// 保证锁能释放
lock.unlock();
}
}
// 显示消息
public void showMessage() {
System.out.println("Hello World!");
}
}
单例子模式在spring
单例模式在Spring源码中被广泛应用,主要用于管理Spring应用程序中的单例对象。
在Spring中,单例模式通常用于实现某些重要的组件,例如事件处理器、资源管理器、应用程序上下文等。这些组件通常是单例的,因为只需要在整个应用程序中创建一个实例来管理特定的资源或处理特定的事件。
1.下面是一个使用单例模式的Spring源码示例,它定义了一个异常处理器:
@ControllerAdvice
public class GlobalExceptionHandler {
private static final Logger logger = LoggerFactory.getLogger(GlobalExceptionHandler.class);
// 处理异常
@ExceptionHandler(Exception.class)
public ResponseEntity<ErrorResponse> handleException(Exception e) {
logger.error("发生异常", e);
ErrorResponse errorResponse = new ErrorResponse("系统异常");
return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).body(errorResponse);
}
}
在上面的代码中,我们定义了一个名为GlobalExceptionHandler的异常处理器,它通过@ControllerAdvice注解声明为控制器通知器,用于处理所有控制器中抛出的异常。
当应用程序发生异常时,GlobalExceptionHandler会捕获异常并记录日志,然后返回一个HTTP状态码为500的响应体。
由于异常处理器使用了单例模式,应用程序可以通过一个异常处理器实例来统一处理所有异常,避免了重复的代码。
2.下面是一个使用单例模式的Spring源码示例,它定义了一个资源管理器:
public class DefaultResourceLoader implements ResourceLoader {
private static final Log logger = LogFactory.getLog(DefaultResourceLoader.class);
// 资源前缀
private static final String CLASSPATH_URL_PREFIX = "classpath:";
// 资源加载器集合
private final Map<ProtocolResolver, Object> protocolResolvers = new LinkedHashMap<>(4);
// 类加载器
private ClassLoader classLoader;
// 添加资源加载器
public void addProtocolResolver(ProtocolResolver resolver) {
Assert.notNull(resolver, "ProtocolResolver must not be null");
this.protocolResolvers.put(resolver, null);
}
// 移除资源加载器
public void removeProtocolResolver(ProtocolResolver resolver) {
Assert.notNull(resolver, "ProtocolResolver must not be null");
this.protocolResolvers.remove(resolver);
}
// 获取资源
public Resource getResource(String location) {
Assert.notNull(location, "Location must not be null");
for (ProtocolResolver protocolResolver : this.protocolResolvers.keySet()) {
Resource resource = protocolResolver.resolve(location, this);
if (resource != null) {
return resource;
}
}
if (location.startsWith(CLASSPATH_URL_PREFIX)) {
return new ClassPathResource(location.substring(CLASSPATH_URL_PREFIX.length()), getClassLoader());
}
try {
// Try to parse the location as a URL...
URL url = new URL(location);
return new UrlResource(url);
}
catch (MalformedURLException ex) {
// No URL -> resolve as resource path.
return getResourceByPath(location);
}