喵星之旅-沉睡的猫咪-单例模式

一、单例是什么?

单例是gof所提到的23种设计模式中的一种,属于创建型设计模式。

1、结构:保证一个类仅有一个实例,并提供一个访问它的全局访问点。

2、动机:对于某些类,只有一个实例是很重要的。尤其是某些具有高级权限的对象,比如线程池可以启动线程,这是对cpu这种极其昂贵资源的调用,单例显得尤为重要。

3、效果:对唯一实例的受控访问。

4、三要素:私有的构造方法;指向自己实例的私有静态引用;以自己实例为返回值的静态的公有方法。

二、饿汉式单例

在类加载初始化的时候就主动创建实例。单例不要实现序列化接口,这里只是为后面错误演示提供方便。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
package create.singleton.hungry;

import java.io.Serializable;

/**
*
* @author bunny~~我是兔子我会喵,我叫喵星兔。
* 饿汉单例 HungrySingleton
*/
public class EhanDanli implements Serializable {

private static final long serialVersionUID = 1L;
private static final EhanDanli instance = new EhanDanli();

private EhanDanli() {
}

public static EhanDanli getInstance() {
return instance;
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
package create.singleton.hungry;
/**
*
* @author bunny~~我是兔子我会喵,我叫喵星兔。
*
*/
public class Test {

public static void main(String[] args) {
EhanDanli s1 = EhanDanli.getInstance();
EhanDanli s2 = EhanDanli.getInstance();
EhanDanli s3 = EhanDanli.getInstance();
System.out.println(s1);
System.out.println(s2);
System.out.println(s3);
System.out.println(s1 == s2);

}

}

三、懒汉式单例

等到真正使用的时候才去创建实例,不用时不去主动创建。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
package create.singleton.lazy;

/**
*
* @author bunny~~我是兔子我会喵,我叫喵星兔。
* 懒汉单例 LazySingleton
*/
public class LanhanDanli {
private LanhanDanli() {
}
private static LanhanDanli instance;
public static LanhanDanli getInstance() {
if (instance == null) {
synchronized (LanhanDanli.class) {
if (instance == null) {
instance = new LanhanDanli();
}
}
}
return instance;
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
package create.singleton.lazy;

/**
*
* @author bunny~~我是兔子我会喵,我叫喵星兔。
*
*/
public class Test {

public static void main(String[] args) {
LanhanDanli s1 = LanhanDanli.getInstance();
LanhanDanli s2 = LanhanDanli.getInstance();
System.out.println(s1);
System.out.println(s2);
System.out.println(s1 == s2);
}

}

四、枚举单例

jvm提供底层保证,不可能出现序列化、反射产生对象的漏洞 。

1
2
3
4
5
6
7
8
9
10
11
12
package create.singleton.register;
/**
*
* @author bunny~~我是兔子我会喵,我叫喵星兔。
* EnumSingleton
*/
public enum ZhuceDanli {
INSTANCE;
public static ZhuceDanli getInstance() {
return INSTANCE;
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
package create.singleton.register;

public class Test {

public static void main(String[] args) {
ZhuceDanli s1 = ZhuceDanli.getInstance();
ZhuceDanli s2 = ZhuceDanli.getInstance();
System.out.println(s1);
System.out.println(s2);
System.out.println(s1 == s2);
}

}

五、单例的破坏

1、反射

单例实现的一个主要因素就是构造器私有化,但是反射可以执行私有化的构造器。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
package create.singleton;

import java.lang.reflect.Constructor;

import create.singleton.hungry.EhanDanli;

/**
*
* @author bunny~~我是兔子我会喵,我叫喵星兔。
* Reflect 反射破坏
*/
public class FanshePohuai {
public static void main(String[] args) throws Exception {
Class<?> clazz = EhanDanli.class;
Constructor c = clazz.getDeclaredConstructor(null);
c.setAccessible(true);
Object s1 = c.newInstance();
Object s2 = c.newInstance();
System.out.println(s1);
System.out.println(s2);
System.out.println(s1 == s2);
}
}

2、序列化、反序列化

破坏原因同上。

1
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
34
35
36
package create.singleton;

import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;

import create.singleton.hungry.EhanDanli;

/**
*
* @author bunny~~我是兔子我会喵,我叫喵星兔。
*Seriable 序列化破坏
*/
public class XunliehuaPohuai {
public static void main(String[] args) throws Exception {
EhanDanli s1 = EhanDanli.getInstance();
EhanDanli s2 = null;
FileOutputStream fos = null;

fos = new FileOutputStream("Seriable.bunny");
ObjectOutputStream oos = new ObjectOutputStream(fos);
oos.writeObject(s1);
oos.flush();
oos.close();
FileInputStream fis = new FileInputStream("Seriable.bunny");
ObjectInputStream ois = new ObjectInputStream(fis);
s2 = (EhanDanli) ois.readObject();
ois.close();

System.out.println(s1);
System.out.println(s2);
System.out.println(s1 == s2);
}
}

六、单例的总结和选择

饿汉式单例:执行效率高,性能高,启动慢。

懒汉式单例:需要考虑锁的问题,没有启动问题,相对难度大。

注册式单例:无法被破坏的单例。实际上就是饿汉式,只不过某些人通过给jdk的官方每人饭里加了鸡腿后,让他们创建了一个霸权模式的饿汉。

重要的对象优选饿汉式。因为重要的内容终究是不多的,没必要在意那些启动时间问题。

不重要的选择懒汉式或者不建议用单例,需要考虑是否必要,因为“单例是邪恶的”。

单例的选择是一把双刃剑,我们要考虑到世界上有一种叫“客户”的动物,外号“上帝”。

对于注册式单例,有赞成使用的,因为安全,不会被破坏。本人不赞成使用。

因为一个完善的生态需要有出生、成长、消亡的过程,而不是永存。健全的生态是平等而不是独裁。如果上帝创造了一种生物,不单没有天敌,而且不会死亡,那么他创造的就是恶魔、灾难,他要有消亡的过程。如果大权永远掌握在一个人手中,那是独裁,就像墨索里尼。

就像丘吉尔和斯大林的对话。斯大林问丘吉尔:“丘吉尔先生,您打赢了仗,人民却罢免了您。”丘吉尔回敬道:“斯大林先生,我打仗就是保卫让人民有罢免我的权力。

而注册式单例不允许他人去罢免他,终究不是我的选择,至于如何选择,仁者见仁。我更愿意追随我最喜欢的二战英雄。

单例到底带来了什么?不是对象的唯一性,最终是受控访问,也就是控制权限,和对象的边界有着很多的联系。如果我们在开发中考虑到了权限的限制,并且想通过对象数量来控制,那就是基于单例模式的,哪怕最后出现的是2个对象。单例模式不限于它具体的代码实现和教条式的定义,只要我们心中所想和它一样,哪怕实现方式不同,那也是单例的灵魂,可能不是单例的代码。设计模式不是让我们照搬他们的代码,而且起到抛砖引玉的目的。我们是通过学习他们的设计思想、处理问题的经验,然后提升改进我们自己的代码。

文章目录
  1. 一、单例是什么?
    1. 1、结构:保证一个类仅有一个实例,并提供一个访问它的全局访问点。
    2. 2、动机:对于某些类,只有一个实例是很重要的。尤其是某些具有高级权限的对象,比如线程池可以启动线程,这是对cpu这种极其昂贵资源的调用,单例显得尤为重要。
    3. 3、效果:对唯一实例的受控访问。
    4. 4、三要素:私有的构造方法;指向自己实例的私有静态引用;以自己实例为返回值的静态的公有方法。
  • 二、饿汉式单例
  • 三、懒汉式单例
  • 四、枚举单例
  • 五、单例的破坏
    1. 1、反射
    2. 2、序列化、反序列化
  • 六、单例的总结和选择
  • |