单例模式

构造方法私有,不允许外部直接创建对象

饿汉模式

创建私有静态类的实例:因为构造私有,静态的话使用类就能得到,这个就能从通过方法获得实例

1
2
3
4
5
6
7
8
9
10
public class Singleton {
//私有且静态
private static Singleton instance = new Singleton();
//构造方法私有
private Singleton (){}
//获取实例的方法,静态
public static Singleton getInstance() {
return instance;
}
}

因为实例被设置为静态,类在加载时就被创建,不管用户是不是要调用

懒汉模式

1
2
3
4
5
6
7
8
9
10
11
12
public class Singleton {
//声明
private static Singleton instance;
private Singleton (){}
//获得实例
public static synchronized Singleton getInstance() {
if (instance == null) {
instance = new Singleton();
}
return instance;
}
}

这种方式具备很好的 lazy loading,能够在多线程中很好的工作,但是,效率很低,99% 情况下不需要同步。
优点:第一次调用才初始化,避免内存浪费。
缺点:必须加锁 synchronized 才能保证单例,但加锁会影响效率。
getInstance() 的性能对应用程序不是很关键(该方法使用不太频繁)。

为了提高性能,需要使用双重锁检查DCL,即 double-checked locking
使用volatile关键字是防止DCL失效

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
public class Singleton {
private volatile static Singleton singleton;
private Singleton (){}
public static Singleton getSingleton() {
//对于大部分情况,单例已经设好
if (singleton == null) {
synchronized (Singleton.class) {
//线程安全
if (singleton == null) {
singleton = new Singleton();
}
}
}
return singleton;
}
}

内部类

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

这种方式能达到双检锁方式一样的功效,但实现更简单。
对静态域使用延迟初始化,应使用这种方式而不是双检锁方式。这种方式只适用于静态域的情况,双检锁方式可在实例域需要延迟初始化时使用。

这种方式同样利用了 classloder 机制来保证初始化 instance 时只有一个线程,它跟饿汉不同的是:
饿汉方式只要 Singleton 类被装载了,那么 instance 就会被实例化(没有达到 lazy loading 效果),
而这种方式是 Singleton 类被装载了,instance 不一定被初始化。
因为 SingletonHolder 类没有被主动使用,只有显示通过调用 getInstance 方法时,才会显示装载 SingletonHolder 类,从而实例化 instance。

想象一下,如果实例化 instance 很消耗资源,所以想让它延迟加载,另外一方面,又不希望在 Singleton 类加载时就实例化,因为不能确保 Singleton 类还可能在其他的地方被主动使用从而被加载,那么这个时候实例化 instance 显然是不合适的。这个时候,这种方式相比饿汉方式就显得很合理。

枚举

1
2
3
4
5
6
7
8
9
10
11
public enum Singleton {
INSTANCE;
public void whateverMethod() {
}
public static void main(String[] args) {
Singleton a = Mo.INSTANCE;
Singleton b = Mo.INSTANCE;
System.out.println(a==b);//true
}
}

这种实现方式还没有被广泛采用,但这是实现单例模式的最佳方法。它更简洁,自动支持序列化机制,绝对防止多次实例化。
这种方式是 Effective Java 作者 Josh Bloch 提倡的方式,它不仅能避免多线程同步问题,而且还自动支持序列化机制,防止反序列化重新创建新的对象,绝对防止多次实例化。不过,由于 JDK1.5 之后才加入 enum 特性,用这种方式写不免让人感觉生疏,在实际工作中,也很少用。
不能通过 reflection attack 来调用私有构造方法。

http://www.runoob.com/design-pattern/singleton-pattern.html

破坏单例

反射

1
2
3
4
5
6
7
8
9
10
11
12
13
//启动JVM的安全检察,在进行反射校验的时候,判断一下是否是“singleton”,如果是,就禁止反射
System.setSecurityManager(new SecurityManager(){
@Override
public void checkPermission(Permission perm) {
if (perm instanceof ReflectPermission && "suppressAccessChecks".equals(perm.getName())) {
for (StackTraceElement elem : Thread.currentThread().getStackTrace()) {
if (elem.getClassName().endsWith("Singleton")) {
throw new SecurityException();
}
}
}
}
});

http://iamzhongyong.iteye.com/blog/2053010

序列化

序列化单例后再反序列化回来会破坏单例,可以重写readResolve方法

1
2
3
4
5
6
7
8
9
10
11
public class Singleton implements Serializable {
private static final Singleton INSTANCE = new Singleton();
public static Singleton getInstance(){
return INSTANCE;
}
public Object readResolve() throws ObjectStreamException {
return INSTANCE; //ensure singleton is returned upon deserialization.
}
}

类加载

两个不同的类加载器,加载单例会产生两个不一样的单例
http://javarevisited.blogspot.com/2011/03/10-interview-questions-on-singleton.html
http://stackoverflow.com/questions/11654876/cracking-singleton-with-other-ways

工厂模式

概念

  • 使用工厂方法代替new操作
  • 工厂模式包括工厂方法模式抽象工厂模式
  • 抽象工厂模式工厂方法模式的扩展

意图

  • 定义一个接口老创建对象,但是让子类来决定哪些类需要被实例化
  • 工厂发放把实例化的工作推迟到子类中去实现

使用场景

  • 有一组类似的对象需要创建
  • 在编码时不能预见需要创建哪种类的实例(超市里买苹果,不知道买蛇果,阿克苏还是冰糖心)
  • 系统需要考虑扩展性

常见应用

  • jdbc
    执行sql的java api,可以为多种数据库提供统一访问

  • spring Bean Factory
    Bean Factory 是spring中IoC的基本容器,是一个产生bean给客户端的工厂

对比

工厂模式工厂模式
工厂模式工厂模式
抽象工厂模式
抽象工厂模式
  • 工厂模式是一种极段的抽象工厂模式,而抽象工厂模式是工厂模式的推广
  • 工厂模式用来创建一个产品的等级结构,而抽象工厂模式用来创建多个产品的等级结构
  • 工厂模式只有一个抽象产品类,而抽象工厂模式有多个抽象产品类

抽象工程关键在于产品之间的抽象关系,所以至少要两个产品;工厂方法在于生成产品,不关注产品间的关系,所以可以只生成一个产品。
抽象工厂中客户端把产品的抽象关系理清楚,在最终使用的时候,一般使用客户端(和其接口),产品之间的关系是被封装固定的;而工厂方法是在最终使用的时候,使用产品本身(和其接口)。
抽象工厂的工厂是类;工厂方法的工厂是方法。

工厂方法模式: 一个抽象产品类,可以派生出多个具体产品类。 一个抽象工厂类,可以派生出多个具体工厂类。 每个具体工厂类只能创建一个具体产品类的实例。
抽象工厂模式: 多个抽象产品类,每个抽象产品类可以派生出多个具体产品类。 一个抽象工厂类,可以派生出多个具体工厂类。 每个具体工厂类可以创建多个具体产品类的实例。
区别: 工厂方法模式只有一个抽象产品类,而抽象工厂模式有多个。 工厂方法模式的具体工厂类只能创建一个具体产品类的实例,而抽象工厂模式可以创建多个