Java设计模式(一)单例模式

单例设计模式

介绍

什么是单例?

单例的意思是一个类中永远只存在一个对象,不能创建多个对象

对象越多越占用内存,有些时候只需要一个对象就可以实现业务,像虚拟机对象,任务管理器对象。单例可以节约内存,提高性能

单例设计模式实现方式

  • 饿汉单例设计模式:通过类获取单例对象的时候,对象已经提前做好了
  • 懒汉单例设计模式:通过类获取单例对象的时候发现没有对象才去创建一个对象
  • 枚举类实现:优点,阻止反射机制带来的影响

饿汉式

1
2
3
4
5
6
7
8
9
10
11
class SingleInstance {

private static SingleInstance ins = new SingleInstance();
/**
* 私有化构造器
*/
private SingleInstance(){}
public static SingleInstance getInstance() {
return ins;
}
}

观察上述代码可以发现,在还没有调用getInstance方法之前,ins就已经被创建出来了,不管使不使用,内存资源始终被占用,这就会造成资源的浪费。通过在静态内部类中来实例化SingleInstance对象可以达到资源节省、延迟加载这一理想的目标。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
class SingleInstance {


/**
* 私有构造器
*/
private SingleInstance(){}

private static class InnerSingleInstance{
private volatile static SingleInstance ins = new SingleInstance();
}
public static SingleInstance getInstance() {
return InnerSingleInstance.ins;
}
}

懒汉式

根据懒汉式设计模式的规则可以写出以下代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
class SingleInstance {

private static SingleInstance ins = null;
/**
* 私有构造器
*/
private SingleInstance(){}
public static SingleInstance getInstance() {
if(ins == null) {
ins = new SingleInstance();
}
return ins;
}
}

在需要的时候才去创建,并且会先判断ins为不为空,为空才会创建。但这样会出现一个问题,因为在实例化化一个对象时,是需要时间的,如果有多个线程同时调用getInstance方法,在对象还没有创建出来之前,都会进入到if块里,这就会导致创建多个对象,与规则不符。

为了避免多个线程调用getInstance方法,可以使用synchronized关键字

1
2
3
4
5
6
7
8
9
10
11
12
13
14
class SingleInstance {

private static SingleInstance ins = null;
/**
* 私有构造器
*/
private SingleInstance(){}
synchronized public static SingleInstance getInstance() {
if(ins == null) {
ins = new SingleInstance();
}
return ins;
}
}

上述代码虽然解决了多个线程共同访问的问题,但在多线程高并发的环境下,会导致系统性能极大的降低。

事实上,我们只需锁定ins = new SingleInstance();这行代码即可。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
class SingleInstance {

private static SingleInstance ins = null;
/**
* 私有构造器
*/
private SingleInstance(){}
public static SingleInstance getInstance() {
if(ins == null) {
synchronized(SingleInstance.class) {
ins = new SingleInstance();
}
}
return ins;
}
}

但这样一来,还是会导致多个对象的创建,因为可能有多个对象进入if块,即使锁定了ins = new SingleInstance();,却并不能阻止后续对象的创建,因此,需要在执行这行代码之前,还需判断对象是否已经被创建。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
class SingleInstance {

private volatile static SingleInstance ins = null;
/**
* 私有构造器
*/
private SingleInstance(){}
public static SingleInstance getInstance() {
if(ins == null) {
synchronized(SingleInstance.class) {
if(ins == null) {
ins = new SingleInstance();
}
}
}
return ins;
}
}

注意,ins字段需要用volatile修饰,被volatile修饰的成员能够确保多个线程都能够正确的处理。

反射破坏单例设计模式

前面两种实现方法,都是基于不考虑反射机制实例化对象,假如利用反射来创建对象,这就破坏了单例设计模式。具体看这段代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
public class Demo1 {
public static void main(String[] args) throws Exception {
SingleInstance ins = SingleInstance.getInstance();
Constructor<SingleInstance> constructor = SingleInstance.class.getDeclaredConstructor();
constructor.setAccessible(true); //setAccessible用于保证反射可调用非public的属性、方法、构造器
SingleInstance intance = constructor.newInstance();
System.out.println(ins == intance); //out:false
}
}

class SingleInstance {
/**
* 私有构造器
*/
private SingleInstance(){}

private static class InnerSingleInstance{
private volatile static SingleInstance ins = new SingleInstance();
}
public static SingleInstance getInstance() {
return InnerSingleInstance.ins;
}
}

枚举类阻止反射破坏

枚举类天生适合单例设计模式,为什么这么说呢,因为JVM 会阻止反射获取枚举类的私有构造方法

1
2
3
4
5
6
enum SingleInstance {
INSTANCE;//这就是枚举类的一个实例
public static SingleInstance getInstance() {
return SingleInstance.INSTANCE;
}
}
-------------本文结束感谢您的阅读-------------
0%