设计模式-单例模式

设计模式-单例模式

单例模式, 就是采取一定方法保证在整个软件系统中, 对某个类只能存在一个对象实例, 并且该类只提供一个取得其对象实例的方法(静态方法)

使用场景

  • 需要频繁进行创建和销毁的对象
  • 创建对象耗时过多或耗费资源过多(重量级对象), 但又常用到的对象
  • 工具类对象
  • 频繁访问数据库或文件的对象(DataSource, session工厂)

八种方式

饿汉式(静态常量)

  • 构造器私有化
  • 类的内部创建对象
  • 对外暴露一个静态的公共方法
1
2
3
4
5
6
7
8
9
10
11
12
13
14
class Singleton {

// 私有化, 外部不能new
private Singleton() {
}

// 内部创建对象
private final static Singleton instance = new Singleton();

public static Singleton getInstance() {
return instance;
}
}

优缺点:

  1. 优点: 写法比较简单, 类装载时候就完成实例化. 避免了线程同步问题
  2. 缺点: 在类装载的时候就完成实例化,没有达到Lazy Loading的效果. 但是, 导致类装载的原因有很多种. 如果从始至终没有用到这个实例, 将会造成==内存浪费==

饿汉式(静态代码块)

  • 与上面相似, 只不过将类实例化放在了静态代码块.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
class Singleton {

// 私有化, 外部不能new
private Singleton() {
}

// 在静态代码块中创建
static {
instance = new Singleton();
}

// 内部创建对象
private final static Singleton instance;

public static Singleton getInstance() {
return instance;
}
}

优缺点:

与上面类似, 通过jvm加载机制可以知道, static代码块和static常量的初始化在jvm编译器优化后, 其实是合并在一起的(等价), 执行先后顺序由代码的先后顺序评定

注意: 因为clinit方法是线程安全, 所以加载类的时候如果static加载的东西太多也会很慢

==内存浪费==

final的作用: 加final修饰的对象, 在连接-准备阶段就已经赋值, 没有final的在初始化阶段赋值

懒汉式(线程不安全)

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

private static Singleton instance;

private Singleton() {
}

// 提供一个静态的共有方法, 只有使用到这个方法的时候,才会创建单例对象
// 懒汉式
public static Singleton getInstance() {
if (instance == null) {
instance = new Singleton();
}
return instance;
}
}

优缺点:

  1. 起到了Lazy Loading的效果, 但是只能在单线程下使用
  2. 多线程下, 一个线程进入了if (instance == null)语句块后, 还没执行后, 另一个线程也进入了这个语句块. 就会产生多个实例.

==实际开发不要使用这种方式==

懒汉式(线程安全, 同步方法)

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

private static Singleton instance;

private Singleton() {
}

// 提供一个静态的共有方法, 加入同步处理的代码, 解决线程安全问题
// 懒汉式
public static synchronized Singleton getInstance() {
if (instance == null) {
instance = new Singleton();
}
return instance;
}
}

优缺点:

  1. 解决了上述的线程不安全的问题
  2. 效率低. 因为只有在第一次才会初始化instance, 其他的时间getInstance方法只是为了获取而已, 这样加入synchronized将会降低很多效率

==实际开发中不推荐==

懒汉式(线程不安全,同步代码块)

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

private static Singleton instance;

private Singleton() {
}

// 提供一个静态的共有方法, 加入同步处理的代码, 解决线程安全问题
// 懒汉式
public static Singleton getInstance() {
if (instance == null) {
synchronized (Singleton.class){
instance = new Singleton();
}
}
return instance;
}
}

优缺点:

  1. 不能起到线程同步的作用: 这样写也没有用! 和上面是一样的, 因为只要if判断结束进入if块中, 线程排队执行同步代码块, 第一个执行完了后, 第二个仍旧要执行! 连线程安全都做不到

==实际开发中不能使用==

双重检查

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

private static volatile Singleton instance;

private Singleton() {
}

// 提供一个静态的共有方法, 加入双重检查锁
// 懒汉式
public static Singleton getInstance() {
if (instance == null) {
synchronized (Singleton.class) {
if (instance == null) {
instance = new Singleton();
}
}
}
return instance;
}
}

优缺点:

  1. Double-Check概念其实是在多线程开发中经常用到的.
  2. 实例化代码只执行了一次
  3. 线程安全; 延迟加载; 效率较高

==开发中推荐使用==

静态内部类

静态内部类的特点就是:

  • 当外部类装载的时候, 内部类是不会装载的.
  • 当getInstance的时候只会装载一次, 还能保证线程安全

jvm在装载类的时候是线程安全的

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

private static volatile Singleton instance;

private Singleton() {
}

// 写一个静态内部类, 该类中有一个静态属性 Singleton
private static class SingletonInstance {

private static final Singleton INSTANCE = new Singleton();
}

public static Singleton getInstance() {
return SingletonInstance.INSTANCE;
}
}

优缺点:

  1. 采用了类装载的机制来保证初始化实例时只有一个线程. jvm帮助我们保证了线程的安全性, 在类初始化时, 别的线程无法进入

==推荐使用==

枚举

1
2
3
4
5
6
enum Singleton{
INSTANCE;
public void sayOK(){
System.out.println("ok");
}
}

优缺点:

  1. jdk1.5+后实现的枚举
  2. 避免多线程同步问题
  3. 防止反序列化重新创建新的对象
  4. 其他方法都可以通过反射破坏, 只有枚举不可以

==强烈推荐==


总结

推荐使用:

  • 饿汉式(静态常量)
  • 饿汉式(静态代码块)
  • 懒汉式(线程不安全)
  • 懒汉式(线程安全,同步方法)
  • 懒汉式(线程不安全,同步代码块)
  • 双重检查
  • 静态内部类
  • 枚举

所以懒汉式团灭 = =

jdk中单例模式的应用: Runtime类, 使用了饿汉式