详解单例模式及其在Sping中的最优实践
作者:鱼仔
博客首页: https://codeease.top
公众号:Java鱼仔
# (一)什么是单例模式
在程序中,每new() 一个对象,就会有一个对象实例生成。有时候在程序中,需要有一个在完整运行状态下只需要生成一个的实例,我们把这种实例称为单例。 抽象到设计模式中,这种只生成一个实例的模式就是单例模式(Singleton)。
# (二)单例模式的实现
单例模式的实现有很多种方式,归根到底是保证new Class()的操作只做一次,在大多数的实现上,会将需要实现单例的类的构造方法改为private修饰,使得无法通过new命令创建对象实例。
# (三)单例模式的代码实现
代码模式有很多种实现方式,主要有饿汉式、懒汉式以及有点特殊的单例注册表(Spring的实现方式)。下面会针对上面的这三种方式各自给出一种实现方式。
# 3.1 饿汉式
饿汉式简单来说就是在项目启动时就将对象实例创建出来,可以用来做需要执行一次的操作:
public class HungrySingleton {
public static final HungrySingleton INSTANCE;
static {
INSTANCE=new HungrySingleton();
}
private HungrySingleton(){
}
}
2
3
4
5
6
7
8
调用时只需要直接调用INSTANCE变量就行:
HungrySingleton instance = HungrySingleton.INSTANCE;
静态代码块饿汉式适合从外部文件中获取数据时使用,我在项目下新建一个info.properties,里面包含一条内容info=hello。饿汉式单例代码如下:
public class HungrySingleton2 {
public static final HungrySingleton2 INSTANCE;
private String info;
static {
Properties properties=new Properties();
try {
properties.load(HungrySingleton2.class.getClassLoader().getResourceAsStream("info.properties"));
} catch (IOException e) {
e.printStackTrace();
}
INSTANCE=new HungrySingleton2(properties.getProperty("info"));
}
private HungrySingleton2(String info){
this.info=info;
}
public String getInfo() {
return info;
}
public void setInfo(String info) {
this.info = info;
}
@Override
public String toString() {
return "Singleton3{" +
"info='" + info + '\'' +
'}';
}
public static void main(String[] args) {
HungrySingleton2 instance = HungrySingleton2.INSTANCE;
System.out.println(instance.getInfo());
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
# 3.2 懒汉式
懒汉式是指当第一次调用时才会生成这个实例,后面再调用就只会使用之前生成的实例。懒汉式是实际编写单例模式代码时用的比较多的方案,下面给出一段十分经典的懒汉式单例模式代码案例:
public class LazySingleton {
private static volatile LazySingleton Instance;
private LazySingleton(){};
public static LazySingleton getInstance(){
if (Instance==null){
synchronized (LazySingleton.class){
if(Instance==null){
Instance=new LazySingleton();
}
}
}
return Instance;
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
在面试中,往往这道题能引出volatile和synchorized这两个知识点。
# 3.3 单例注册表
我看网上很少有人介绍这种单例模式,单例注册表是Spring中Bean单例的核心实现方案。可以通过一个ConcurrentHashMap存储Bean对象,保证Bean名称唯一的情况下也能保证线程安全。下面是单例注册表的简单实现:
public class RegSingleton {
private final static Map<String, Object> singletonObjects = new ConcurrentHashMap<>(16);
private RegSingleton(){}
public static Object getInstance(String className){
if (StringUtils.isEmpty(className)) {
return null;
}
if (singletonObjects.get(className) == null) {
try {
singletonObjects.put(className, Class.forName(className).newInstance());
} catch (Exception e) {
e.printStackTrace();
}
}
return singletonObjects.get(className);
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
新建一个测试类:
public class User {
private String id;
public void setId(String id) {
this.id = id;
}
public String getId() {
return id;
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (!(o instanceof User)) return false;
User user = (User) o;
return Objects.equals(getId(), user.getId());
}
@Override
public int hashCode() {
return Objects.hash(getId());
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
最后测试单例是否生效:
public class Main {
public static void main(String[] args) {
User user1 = (User) RegSingleton.getInstance("com.javayz.singleton.User");
User user2 = (User) RegSingleton.getInstance("com.javayz.singleton.User");
System.out.println(user1==user2);
}
}
2
3
4
5
6
7
# (四)单例模式在Spring中的最佳实践
单例模式的最佳实践就是Spring中的Bean了,Spring中的Bean默认都是单例模式,Spring实现单例的方式就采用了单例注册表的方式。
首先在Bean工厂中,如果设置了Spring的Bean模式为单例模式,Spring就会通过getSingleton的方式去获取单例Bean对象。
接着就会进入到DefaultSingletonBeanRegistry类的getSingleton方法中,忽略掉其他代码,只看单例Bean生成相关代码
public class DefaultSingletonBeanRegistry extends SimpleAliasRegistry implements SingletonBeanRegistry {
/** Cache of singleton objects: bean name to bean instance. */
private final Map<String, Object> singletonObjects = new ConcurrentHashMap<>(256);
public Object getSingleton(String beanName, ObjectFactory<?> singletonFactory) {
//一系列处理操作
}
finally {
if (recordSuppressedExceptions) {
this.suppressedExceptions = null;
}
afterSingletonCreation(beanName);
}
if (newSingleton) {
//如果实例对象不存在,注册到单例注册表中
addSingleton(beanName, singletonObject);
}
}
return singletonObject;
}
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
对应的addSingleton方法就是将对象加入到单例注册表中:
# (五)总结
至此,单例模式的内容差不多就结束了,结合源码看设计模式每次都有新收获。我是鱼仔,我们下期再见!