您的位置:首页 > Web前端

剑指offer - 实现Singleton模式

2017-04-24 15:59 281 查看
题目:设计一个类,只能生成该类的一个实例 

《剑指offer》上为C#版本,并查阅网上大神的答案,稍作整理,有些不懂的地方供以后慢慢学习。 
先贴出面试时代码,原理及思路在四:
public class Singleton{
private static volatile Singleton instance = null;
private Singleton(){}
public static Singleton getInstance() {
if (instance == null) {
synchronized (Singleton.class) {
if (instance == null){
Singleton instance = new Singleton();
}
}
}
return instance;
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
1
2
3
4
5
6
7
8
9
10
11
12
13
14

单例模式的应用场景: 

在计算机系统中,线程池、缓存、日志对象、对话框、打印机、显卡的驱动程序对象常被设计成单例。这些应用都或多或少具有资源管理器的功能。每台计算机可以有若干个打印机,但只能有一个Printer Spooler(打印后台处理服务),以避免两个打印作业同时输出到打印机中。每台计算机可以有若干通信端口,系统应当集中管理这些通信端口,以避免一个通信端口同时被两个请求同时调用。


一、加载类时就初始化此实例

public class Singleton{
private static Singleton instance = new Singleton();
//private:除了Singleton类内部,其它地方无法访问该变量
//static:确保是静态类变量,这样可以通过类名来访问,无须实例;
// 有的版本加上final修饰符,并不知道为什么??
private Singleton(){}
//需要显式定义构造函数,并将其设置为private,这样在Singleton类之外new的话编译器就会报错,即使是Singleton的子类也不行
public static Singleton getInstance(){
return instance;
}
//public:这样外部才能调用;
//static:这样外部才能通过类名来访问,否则获取不到单例对象;
}
1
2
3
4
5
6
7
8
9
10
11
12
13
1
2
3
4
5
6
7
8
9
10
11
12
13

这就是传说中的饿汉模式,比较简单,不存在多线程同步问题,避免了synchronized所造成的性能问题;当类Singleton被加载时,会初始化static的instance,并分配内存空间,从这以后,这个static的instance对象便一直占着这段内存(即便你还没有用到这个实例),当类被卸载时,静态变量被摧毁,并释放所占有的内存,因此在某些特定条件下会耗费内存。


二、使用懒加载

public class Singleton{
private static Singleton instance = null;
private Singleton(){}
public static Singleton getInstance(){
if(instance == null)
instance = new Singleton();
return instance;
}
}
1
2
3
4
5
6
7
8
9
1
2
3
4
5
6
7
8
9

这就是饱汉式,比较简单,当类SingletonTest被加载的时候,static的instance未被创建(就没分配内存啦),当getInstance方法第一次被调用时,初始化instance变量并分配内存,因此在某些特定条件下会节约了内存;但是并发环境下可能出现多次调用getInstance(),初始化多个SingletonTest实例。


三、考虑多线程(常用)

public class Singleton{
private static Singleton instance = null;
private Singleton(){}
public static synchronized Singleton getInstance(){//竞争同一把锁,即使在单例对象不为空的时候
if(instance == null)
instance = new Singleton();
return instance;
}
}
1
2
3
4
5
6
7
8
9
1
2
3
4
5
6
7
8
9

使用synchronized关键字避免多线程访问时出现多个Singleton实例;同步方法频繁调用时,效率略低。


四、考虑性能

//面试时用这个版本就对了
public class Singleton{
//使用volatile保证了多线程访问时instance变量的可见性,避免了instance初始化时其他变量属性还没赋值完时,被另外线程调用
private static volatile Singleton instance = null;
private static Singleton instance = null;
private Singleton(){}
public static Singleton getInstance() {
if (instance == null) {
synchronized (Singleton.class) {
if (instance == null){
Singleton instance = new Singleton();
}
}
}
return instance;
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
public class Singleton{
private static Singleton instance = null;
private Singleton(){}
public static Singleton getInstance() {
if (instance == null) {
////同步代码块(对象未初始化时,使用同步代码块,保证多线程访问时对象在第一次创建后,不再重复被创建)
synchronized (Singleton.class) {
if (instance == null){//使用double check方式,只有在单例对象为空时才竞争锁
Singleton t = new Singleton();
instance = t;
}
}
}
return instance;
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16

此为线程安全且效率比较高的版本。

synchronized关键字修饰getInstance()方法,会导致所有调用getInstance()方法的线程都要竞争同一把锁,即使在单例对象已经生成好的情况下。这里使用double check的方式。(即先判断是否为null,为空的话就竞争锁,不为空就直接返回Instance)。另外,还需要注意instance在初始化过程中,可能已经不为null,这样有可能会导致程序崩溃,因此这里加了个临时变量t,确保初始化完成之后再赋值给instance。(这里的t的作用和上面的volatile的作用是一样的,两者使用一个就可以了) 

由于Java会对指令进行重排序,因此有可能会出现instance不为null,但是构造函数还没有执行完的情况,这时如果使用这个对象就有可能出现不可预期的情况,崩溃是一种可能。


五、更简单的方法

public class Singleton{
private Singleton(){}
public static Singleton getInstance(){
return NestedClass.instance;
}
private static class NestedClass{
private static Singleton instance = new Singleton();
}
}
1
2
3
4
5
6
7
8
9
1
2
3
4
5
6
7
8
9

内部定义一个私有类型NestedClass,当第一次用到这个嵌套类型的时候,会调用静态构造函数创建Singleton实例instance。类型NestedClass只在Singleton.getInstance()中用到,由于其private属性他人无法使用NestedClass类型。因此当第一次通过Singleton.getInstance()得到Singleton的实例时,就会调用NestedClass的构造函数创建instance,如果不调用Singleton.getInstance()就不会触发调用NestedClass,就不会创建实例这样就做到按需创建,提高空间使用效率。

思路及总结: 

单例模式要求在系统运行时,存在唯一的实例对象。 

“存在唯一”,首先考虑到静态变量,但此处是实例对象,所以先试试在类内部创建一个该类的成员变量,或者可以考虑下静态内部类。

Java中单例模式定义:“一个类有且仅有一个实例,并且自行实例化向整个系统提供。”

如何保证一个类只有一个实例并且这个实例易于被访问呢?定义一个全局变量可以确保对象随时都可以被访问,但不能防止我们实例化多个对象。一个更好的解决办法是让类自身负责保存它的唯一实例。这个类可以保证没有其他实例被创建,并且它可以提供一个访问该实例的方法。 

显然单例模式的要点有三个;一是某个类只能有一个实例;二是它必须自行创建这个实例;三是它必须自行向整个系统提供这个实例。 

从具体实现角度来说,就是以下三点:一是单例模式的类只提供私有的构造函数,二是类定义中含有一个该类的静态私有对象,三是该类提供了一个静态的公有的函数用于创建或获取它本身的静态私有对象。

参考:http://www.cnblogs.com/yinxiaoqiexuxing/p/5605338.html 

以下值得注意:

事实上,可以通过Java反射机制来实例化private类型的构造方法,此时基本上会使所有的Java单例实现失效。

//以下为反射使单例模式失效的例子
public class SingletonTest {
private static SingletonTest instance;

private SingletonTest() {
}

public static SingletonTest getInstance() {

if (instance == null) {
synchronized (SingletonTest.class) {
if (instance == null) {
instance = new SingletonTest();
}
}
}
return instance;
}

public static void main(String[] args) {
try {
SingletonTest instance1 = SingletonTest.class.newInstance();
SingletonTest instance2 = SingletonTest.getInstance();
System.out.println(instance1);
System.out.println(instance2);
} catch (InstantiationException e) {
e.printStackTrace();
} catch (IllegalAccessException e) {
e.printStackTrace();
}
}
}// instace1、instance2的地址是不一样的,是两个对象
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
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

如有不懂还可以参考http://www.iteye.com/topic/652440 

http://www.cs.umd.edu/~pugh/java/memoryModel/DoubleCheckedLocking.html
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: