1、单例模式介绍html
单例模式:保证一个类只有一个实例,而且提供一个访问该实例的全局访问点。java
单例模式优势:数据库
1.只生成一个实例,系统开销比较小windows
2.单例模式能够在系统设置全局的访问点,优化共享资源的访问。设计模式
常见单例模式分类:安全
主要:并发
饿汉式(线程安全,调用效率高,可是不能延时加载)ide
懒汉式(线程安全,调用效率不高,可是能够延时加载)高并发
其余:学习
双重检测锁式(因为JVM底层内部模型缘由,偶尔会出问题。不建议使用)
静态内部类式(线程安全,调用效率高。可是能够延时加载)
枚举单例(线程安全,调用效率高,不能延时加载)
2、单例模式实例代码
一、懒汉式
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
|
package
com.fz.singleton;
/**
* 饿汉式单例:所谓饿汉式,就是比较饿。当类一加载的时候就直接new了一个静态实例。无论后面有没有用到该实例
*/
public
class
Singleton1 {
/**
* 一、提供一个静态变量。
* 当类加载器加载该类时,就new一个实例出来。从属于这个类。无论后面用不用这个类。因此没有延时加载功能
*/
private
static
Singleton1 instance =
new
Singleton1();
/**
* 二、私有化构造器:外部是不能直接new该对象的
*/
private
Singleton1(){}
/**
* 三、对外提供一个公共方法来获取这个惟一对象(方法没有使用synchronized则调用效率高)
* @return
*/
public
static
Singleton1 getInstance(){
return
instance;
}
}
|
二、饿汉式
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
|
package
com.fz.singleton;
/**
* 懒汉式单例:比较懒,一开始不初始化实例。等何时用就何时初始化.避免资源浪费
*/
public
class
Singleton2 {
/**
* 一、声明一个静态实例,不给它初始化。等何时用就何时初始化。节省资源
*/
private
static
Singleton2 instance;
/**
* 二、依然私有化构造器,对外不让new
*/
private
Singleton2(){}
/**
* 三、对外提供一个获取实例的方法,由于静态属性没有实例化。
* 假如高并发的时候,有可能会同时调用该方法。形成new出多个实例。因此须要加上同步synchronized,所以调用效率不高
* 在方法上加同步,是整个方法都同步。效率不高
* @return
*/
public
synchronized
static
Singleton2 getInstance(){
if
(instance ==
null
) {
//第一次调用时为空,则直接new一个
instance =
new
Singleton2();
}
//以后第二次再调用的时候就已经初始化了,不用再new。直接返回
return
instance;
}
}
|
三、双重检索方式
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
37
38
|
package
com.fz.singleton;
/**
* 双重检索单例模式
* 将锁加在判断实例为空的地方,不加在方法上
*/
public
class
Singleton3 {
/**
* 一、提供未实例化的静态实例
*/
private
static
Singleton3 instance =
null
;
/**
* 二、私有化构造器
*/
private
Singleton3(){}
/**
* 三、对外提供获取实例的方法
* 可是同步的时候将锁放到第一次获取实例的时候,这样的好处就是只有第一次会同步。效率高
* @return
*/
public
static
Singleton3 getInstance(){
if
(instance ==
null
) {
Singleton3 s3;
synchronized
(Singleton3.
class
) {
s3 = instance;
if
(s3 ==
null
) {
synchronized
(Singleton3.
class
) {
if
(s3 ==
null
) {
s3 =
new
Singleton3();
}
}
instance = s3;
}
}
}
return
instance;
}
}
|
四、静态内部类方式
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
|
package
com.fz.singleton;
/**
* 静态内部类单例实现
*/
public
class
Singleton4 {
/**
* 一、私有化构造器
*/
private
Singleton4(){}
/**
* 二、声明一个静态内部类,在静态内部类内部提供一个外部类的实例(常量,不可改变)
* 初始化Singleton4 的时候不会初始化SingletonClassInstance,实现了延时加载。而且线程安全
*/
private
static
class
SingletonClassInstance{
//该实例只读,无论谁都不能修改
private
static
final
Singleton4 instance =
new
Singleton4();
}
/**
* 三、对外提供一个获取实例的方法:直接返回静态内部类中的那个常量实例
* 调用的时候没有同步等待,因此效率也高
* @return
*/
public
static
Singleton4 getInstance(){
return
SingletonClassInstance.instance;
}
}
|
五、枚举单例
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
|
package
com.fz.singleton;
/**
* 枚举实现单例模式(枚举自己就是单例)
*/
public
enum
Singleton5 {
/**
* 定义一个枚举元素,它就是一个单例的实例了。
*/
INSTANCE;
/**
* 对枚举的一些操做
*/
public
void
singletonOperation(){
}
}
|
3、如何破解单例模式?
a、经过反射破解(不包括枚举,由于枚举自己是单例,是由JVM管理的)
b、经过反序列化
一、经过反射破解单例实例代码
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
|
package
com.fz.singleton;
import
java.lang.reflect.Constructor;
/**
* 经过反射破解单例模式
*/
public
class
TestReflect {
public
static
void
main(String[] args)
throws
Exception {
Singleton6 s1 = Singleton6.getInstance();
Singleton6 s2 = Singleton6.getInstance();
System.out.println(s1 == s2);
//true
//经过反射破解
Class<Singleton6> clazz = (Class<Singleton6>) Class.forName(Singleton6.
class
.getName());
Constructor<Singleton6> c = clazz.getDeclaredConstructor(
null
);
//得到无参构造器
c.setAccessible(
true
);
//跳过检查:能够访问private构造器
Singleton6 s3 = c.newInstance();
//此时会报错:没有权限访问私有构造器
Singleton6 s4 = c.newInstance();
System.out.println(s3==s4);
//不加c.setAccessible(true)则会报错。此时的结果就是false,得到的就是两个对象
}
}
|
如何防止反射破解单例模式呢?
在Singleton6构造的时候,假如不是第一次就直接抛出异常。不让建立。这样第二次构建的话就直接抛出异常了。
1
2
3
4
5
6
|
private
Singleton6(){
if
(instance !=
null
) {
//若是不是第一次构建,则直接抛出异常。不让建立
throw
new
RuntimeException();
}
}
|
二、经过序列化和反序列化构建对象
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
|
package
com.fz.singleton;
import
java.io.FileInputStream;
import
java.io.FileOutputStream;
import
java.io.ObjectInputStream;
import
java.io.ObjectOutputStream;
import
java.lang.reflect.Constructor;
/**
* 经过反射破解单例模式
*/
public
class
TestReflect {
public
static
void
main(String[] args)
throws
Exception {
Singleton6 s1 = Singleton6.getInstance();
Singleton6 s2 = Singleton6.getInstance();
//经过反序列化构建对象:经过序列化将s1存储到硬盘上,而后再经过反序列化把s1再构建出来
FileOutputStream fos =
new
FileOutputStream(
"e:/a.txt"
);
ObjectOutputStream oos =
new
ObjectOutputStream(fos);
oos.writeObject(s1);
oos.close();
fos.close();
//经过反序列化将s1对象再构建出来
ObjectInputStream ois =
new
ObjectInputStream(
new
FileInputStream(
"e:/a.txt"
));
Singleton6 s5 = (Singleton6) ois.readObject();
System.out.println(s5);
//此时打印出一个新对象
System.out.println(s1==s5);
//false
}
}
|
防止反序列化构建对象
在Singleton6中定义一个方法,此时结果就会同样了。System.out.println(s1==s5);结果就是true了
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
37
38
39
40
41
|
package
com.fz.singleton;
import
java.io.ObjectStreamException;
import
java.io.Serializable;
/**
* 用于测试反射破解的单例类
*/
public
class
Singleton6
implements
Serializable {
/**
* 一、提供一个静态变量。
* 当类加载器加载该类时,就new一个实例出来。从属于这个类。无论后面用不用这个类。因此没有延时加载功能
*/
private
static
Singleton6 instance =
new
Singleton6();
/**
* 二、私有化构造器:外部是不能直接new该对象的
*/
private
Singleton6(){
if
(instance !=
null
) {
//若是不是第一次构建,则直接抛出异常。不让建立
throw
new
RuntimeException();
}
}
/**
* 三、对外提供一个公共方法来获取这个惟一对象(方法没有使用synchronized则调用效率高)
* @return
*/
public
static
Singleton6 getInstance(){
return
instance;
}
/**
* 反序列化时,若是定义了readResolve()则直接返回该方法指定的实例。不会再单首创建新对象!
* @return
* @throws ObjectStreamException
*/
private
Object readResolve()
throws
ObjectStreamException{
return
instance;
}
}
|
测试几种单例的速度
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
37
38
39
|
package
com.fz.singleton;
import
java.util.concurrent.CountDownLatch;
/**
* 测试几种单例模式的速度
*/
public
class
TestSingleton {
public
static
void
main(String[] args)
throws
InterruptedException {
long
start = System.currentTimeMillis();
int
threadNum =
10
;
//10个线程
final
CountDownLatch countDownLatch =
new
CountDownLatch(threadNum);
for
(
int
i =
0
; i < threadNum; i++) {
new
Thread(
new
Runnable() {
@Override
public
void
run() {
for
(
int
i =
0
; i <
100000
; i++) {
Object o = Singleton5.INSTANCE;
}
countDownLatch.countDown();
//计数器-1
}
}).start();
}
countDownLatch.await();
//main线程阻塞
long
end = System.currentTimeMillis();
System.out.println(
"耗时:"
+(end-start));
/**
* 结果(毫秒):
* Singleton1(饿汉式)耗时:5
* Singleton2(懒汉式)耗时:227
* Singleton3(双重检索式)耗时:7
* Singleton4(静态内部类式)耗时:40
* Singleton5(枚举式)耗时:5
*/
}
}
|
4、总结
如何选用?
枚举式 好于 饿汉式
静态内部类式 好于 懒汉式
常见应用场景
windows的任务管理器
网站的计数器
数据库的链接池
Application容器也是单例
Spring中每一个bean默认也是单例
Servlet中,每一个servlet也是单例
参考资料:
大话设计模式(带目录完整版).pdf
HEAD_FIRST设计模式(中文版).pdf
尚学堂_高淇_java300集最全视频教程_【GOF23设计模式】