设计模式实战——开发中常用到的单例模式
2020-03-02 16:01:19来源:博客园 阅读 ()
设计模式实战——开发中常用到的单例模式
本系列博客是自己在学习设计模式过程中收集整理的文章集合,其他文章参看设计模式传送门
单例模式简介
单例模式的目的是保证系统中只有类的一个实例对象,并且提供一个全局的入口点来获取并使用这个实例对象。
使用单例模式可以防止用户“胡乱”创建对象,耗费内存。而且有些对象从逻辑上来讲一个系统中只应该存在一个,比如说Runtime
类,使用单例模式也能很好的保证这一点。
本文介绍几个我们平时开发过程中常用到的单例模式场景,来加深我们对单例模式的理解。
JDK中的单例模式
Runtime
类封装了Java运行时的环境。每一个java程序实际上都是启动了一个JVM进程,那么每个JVM进程都是对应这一个Runtime实例,此实例是由JVM为其实例化的。每个 Java 应用程序都有一个 Runtime 类实例,使应用程序能够与其运行的环境相连接。
由于Java是单进程的,所以,在一个JVM中,Runtime的实例应该只有一个。所以应该使用单例来实现。
public class Runtime {
private static Runtime currentRuntime = new Runtime();
public static Runtime getRuntime() {
return currentRuntime;
}
private Runtime() {}
}
以上代码为JDK中Runtime
类的部分实现,可以看到,这其实是饿汉式单例模式。在该类第一次被classloader加载的时候,这个实例就被创建出来了。
Spring
中的单例模式
我们知道在Spring
中默认注入的Bean都是单例,那么Spring中的单例是怎么生成的呢?我们来看下Spring生成Bean的代码。
@Nullable
protected Object getSingleton(String beanName, boolean allowEarlyReference) {
Object singletonObject = this.singletonObjects.get(beanName);
if (singletonObject == null && isSingletonCurrentlyInCreation(beanName)) {
synchronized (this.singletonObjects) {
singletonObject = this.earlySingletonObjects.get(beanName);
if (singletonObject == null && allowEarlyReference) {
ObjectFactory<?> singletonFactory = this.singletonFactories.get(beanName);
if (singletonFactory != null) {
singletonObject = singletonFactory.getObject();
this.earlySingletonObjects.put(beanName, singletonObject);
this.singletonFactories.remove(beanName);
}
}
}
}
return singletonObject;
}
spring依赖注入时,使用了双重判断加锁的单例模式,首先从缓存MAP中获取bean实例,如果为null,对缓存map加锁,然后再从缓存中获取bean,如果继续为null,就创建一个bean。
Spring并没有使用私有构造方法来创建bean,而是通过singletonFactory.getObject()返回具体beanName对应的ObjectFactory来创建bean。实际上是调用了AbstractAutowireCapableBeanFactory的doCreateBean方法,返回了BeanWrapper包装并创建的bean实例。
MyBatis
中的单例模式
1. ErrorContext
ErrorContext是用在每个线程范围内的单例,用于记录该线程的执行环境错误信息。
public class ErrorContext {
private static final String LINE_SEPARATOR = System.getProperty("line.separator","\n");
private static final ThreadLocal<ErrorContext> LOCAL = new ThreadLocal<ErrorContext>();
private ErrorContext stored;
private String resource;
private String activity;
private String object;
private String message;
private String sql;
private Throwable cause;
private ErrorContext() {
}
public static ErrorContext instance() {
ErrorContext context = LOCAL.get();
if (context == null) {
context = new ErrorContext();
LOCAL.set(context);
}
return context;
}
}
构造函数是private修饰,具有一个static的局部instance变量和一个获取instance变量的方法,在获取实例的方法中,先判断是否为空如果是的话就先创建,然后返回构造好的对象。
只是这里有个有趣的地方是,LOCAL的静态实例变量使用了ThreadLocal修饰,也就是说它属于每个线程各自的数据,而在instance()方法中,先获取本线程的该实例,如果没有就创建该线程独有的ErrorContext。
也就是说ErrorContext
是线程范围内的单例,而不是全局范围内(JVM内)的单例。
2. VFS
public abstract class VFS {
private static final Log log = LogFactory.getLog(VFS.class);
/** The built-in implementations. */
public static final Class<?>[] IMPLEMENTATIONS = { JBoss6VFS.class, DefaultVFS.class };
/** The list to which implementations are added by {@link #addImplClass(Class)}. */
public static final List<Class<? extends VFS>> USER_IMPLEMENTATIONS = new ArrayList<Class<? extends VFS>>();
/** Singleton instance. */
private static VFS instance;
/**
* Get the singleton {@link VFS} instance. If no {@link VFS} implementation can be found for the
* current environment, then this method returns null.
*/
@SuppressWarnings("unchecked")
public static VFS getInstance() {
if (instance != null) {
return instance;
}
}
VFS是MyBatis中提供的文件系统类,存在感比较低。但是我们看下这个类的源代码的话,的确是很标准的单例模式。
Log4j中的单例
Log4jLoggerFactory
创建Logger时也是用的单例模式。代码如下:
private static ConcurrentMap<String, Logger> log4jLoggers = new ConcurrentHashMap();
Log4jLoggerFactory() {
}
public static Logger getLogger(String name) {
Logger instance = (Logger)log4jLoggers.get(name);
if (instance != null) {
return instance;
} else {
Logger newInstance = new Logger(name);
Logger oldInstance = (Logger)log4jLoggers.putIfAbsent(name, newInstance);
return oldInstance == null ? newInstance : oldInstance;
}
}
public static Logger getLogger(String name, LoggerFactory loggerFactory) {
Logger instance = (Logger)log4jLoggers.get(name);
if (instance != null) {
return instance;
} else {
Logger newInstance = loggerFactory.makeNewLoggerInstance(name);
Logger oldInstance = (Logger)log4jLoggers.putIfAbsent(name, newInstance);
return oldInstance == null ? newInstance : oldInstance;
}
}
PS:有一个问题,不同的多个Logger向同一个文件中打日志时,是怎么保证高效并且线程安全的???
参考
- https://my.oschina.net/u/1024107/blog/1836488
原文链接:https://www.cnblogs.com/54chensongxia/p/12396261.html
如有疑问请与原作者联系
标签:
版权申明:本站文章部分自网络,如有侵权,请联系:west999com@outlook.com
特别注意:本站所有转载文章言论不代表本站观点,本站所提供的摄影照片,插画,设计作品,如需使用,请与原作者联系,版权归原作者所有
- 掌握SpringBoot-2.3的容器探针:实战篇 2020-06-11
- 为什么阿里巴巴Java开发手册中不允许魔法值出现在代码中? 2020-06-09
- 设计模式-委派/策略模式 2020-06-09
- Java框架之Hibernate实战篇 2020-06-09
- RocketMQ4.4 入门进阶+实战 2020-06-08
IDC资讯: 主机资讯 注册资讯 托管资讯 vps资讯 网站建设
网站运营: 建站经验 策划盈利 搜索优化 网站推广 免费资源
网络编程: Asp.Net编程 Asp编程 Php编程 Xml编程 Access Mssql Mysql 其它
服务器技术: Web服务器 Ftp服务器 Mail服务器 Dns服务器 安全防护
软件技巧: 其它软件 Word Excel Powerpoint Ghost Vista QQ空间 QQ FlashGet 迅雷
网页制作: FrontPages Dreamweaver Javascript css photoshop fireworks Flash