按照目的,设计模式可以分为以下三种用途:
1.创建型模式:用来处理对象的创建过程
2.结构型模式:用来处理类或者对象的组合3.行为型模式:用来对类或对象怎样交互和怎样分配职责进行描述
工厂方法模式(Factory Method Pattern)
抽象工厂模式(Abstract Factory Pattern)
建造者模式(Builder Pattern)
原型模式(Prototype Pattern)
单例模式(Singleton Pattern)
结构型模式用来处理类或者对象的组合,主要包含以下7种设计模式:
适配器模式(Adapter Pattern)
桥接模式(Bridge Pattern)
组合模式(Composite Pattern)
装饰者模式(Decorator Pattern)
外观模式(Facade Pattern)
享元模式(Flyweight Pattern)
代理模式(Proxy Pattern)
行为型模式用来对类或对象怎样交互和怎样分配职责进行描述,主要包含以下11种设计模式:
责任链模式(Chain of Responsibility Pattern)
命令模式(Command Pattern)
解释器模式(Interpreter Pattern)
迭代器模式(Iterator Pattern)
中介者模式(Mediator Pattern)
备忘录模式(Memento Pattern)
观察者模式(Observer Pattern)
状态模式(State Pattern)
策略模式(Strategy Pattern)
模板方法模式(Template Method Pattern)
访问者模式(Visitor Pattern)
本节主要讲解单例模式(Singleton Pattern)又叫单态模式
单例模式有以下特点:
1、单例类只能有一个实例。
2、单例类必须自己创建自己的唯一实例。
3、单例类必须给所有其他对象提供这一实例。单例模式确保某个类只有一个实例,而且自行实例化并向整个系统提供这个实例。
单例模式的使用场景:
在计算机系统中,线程池、缓存、日志对象、对话框、打印机、显卡的驱动程序对象常被设计成单例。这些应用都或多或少具备资源管理器的功能。
例如:
每台计算机可以有若干个打印机,但只能有一个Printer Spooler【打印机要工作正常,您需要确保机器上的Printer Spooler 这个服务处于运行状态。如果您的Printer Spooler服务没有开启,可以在服务中手动开启Printer Spooler】,以避免两个打印作业同时输出到同一个打印机中。
方法一:饿汉模式
1.饿汉模式单例(线程安全)
public class Singleton1 {
//将自身的实例对象设置为一个属性,并加上static和final修饰符,这样就能保证内存中只有一个对象
private static final Singleton1 instance = new Singleton1();
//把构造方法设置为私有化,这样其他类就不能通过构造方法创建实例了
private Singleton1() {
}
//通过这个静态方法向外界提供这个类的实例,而且这个实例在一开始就已经创建了
public static Singleton1 getInstance() {
return instance;
}
}
这种方式是基于classloder机制避免了多线程的同步问题。instance在类装载时就实例化,虽然导致类装载的原因有很多种,在单例模式中大多数都是调用getInstance方法, 但是也不能确定有其他的方式(或者其他的静态方法)导致类装载,这时候初始化instance显然没有达到lazy loading的效果。
2.饿汉变种单例(线程安全)
public class Singleton1 {
private Singleton1 instance = null;
static {
instance = new Singleton1 ();
}
private Singleton1 (){
}
public static Singleton1 getInstance() {
return this.instance;
}
}
实际上和饿汉模式是一样的!只是看起来不太一样!
1.真正的懒汉模式(线程不安全)
public class Singleton2 {
//在第一次调用的时候实例化自己
private static Singleton2 instance2 = null;
private Singleton() {} //避免被外部访问//静态工厂方法,要加上同步
public static Singleton2 getInstance() {
if (instance2 == null)
instance2 = new Singleton2();
return instance2;
}
}
public class Singleton2 {
//在第一次调用的时候实例化自己
private static Singleton2 instance2 = null;
private Singleton() {} //避免被外部访问//静态工厂方法,要加上同步
public static synchronized Singleton2 getInstance() {
if (instance2 == null)
instance2 = new Singleton2();
return instance2;
}
}
3.懒汉模式中的双重检查锁定(线程安全)
public class Singleton2 {
//在第一次调用的时候实例化自己
private static Singleton2 instance2 = null;
//避免被外部访问
private Singleton() {
}
//静态工厂方法,要加上同步
public static Singleton2 getInstance() {
if (instance2 == null) {
synchronized (Singleton2 .class) {
if (instance2 == null) {
instance2 = new Singleton2 ();
}
}
}
return instance2;}
}
4.懒汉模式中的静态内部类
public class Singleton2 {
private static class LazyHolder {
private static final Singleton2 INSTANCE = new Singleton2 ();
}
private Singleton2 (){
}
public static final Singleton2 getInstance() {
return LazyHolder.INSTANCE;
}
}
第4种比上面2、3都好一些,既实现了线程安全,又避免了同步带来的性能影响。
这种方式同样利用了classloder的机制来保证初始化instance时只有一个线程,
它跟饿汉模式不同的是(很细微的差别):
饿汉模式是只要Singleton类被装载了,那么instance就会被实例化(没有达到lazy loading效果),
而这种方式是Singleton类被装载了,instance不一定被初始化。因为LazyHolder类没有被主动使用,只有显示通过调用getInstance方法时,才会显示装载LazyHolder类,从而实例化instance。想象一下,如果实例化instance很消耗资源,我想让他延迟加载,另外一方面,我不希望在Singleton2 类加载时就实例化,因为我不能确保Singleton2 类还可能在其他的地方被主动使用从而被加载,那么这个时候实例化instance显然是不合适的。这个时候,这种方式相比饿汉模式就显得很合理。
第2种:在方法调用上加了同步,虽然线程安全了,但是每次都要同步,会影响性能,毕竟99%的情况下是不需要同步的,性能损耗太大。
第3种:在getInstance中做了两次null检查,确保了只有第一次调用单例的时候才会做同步,这样也是线程安全的,同时避免了每次都同步的性能损耗。
第4种:利用了 classloader的机制来保证初始化instance时只有一个线程,所以也是线程安全的,同时没有性能损耗,所以一般我倾向于使用这一种。
方法三:枚举
public enum Singleton {
INSTANCE;
public void whateverMethod() {
}
}
这种方式是Effective Java作者Josh Bloch 提倡的方式,它不仅能避免多线程同步问题,而且还能防止反序列化重新创建新的对象,可谓是很坚强的壁垒啊。
饿汉模式和懒汉模式区别
1.初始化上:
饿汉就是类一旦加载,就把单例初始化完成,保证getInstance的时候,单例是已经存在的了,而懒汉比较懒,只有当调用getInstance的时候,才回去初始化这个单例。
2、线程安全:
从代码上不难看出饿汉式是线程安全的,可以直接用于多线程而不会出现问题。
懒汉模式(第一种)本身是非线程安全的,正常使用的时候应该加上线程安全的结局方案,
上面(2、3、4)三种方案可以考虑一下,只是上面三种实现在资源加载和性能方面稍微有些区别。
3、资源加载和性能:
饿汉模式在类创建的同时就实例化一个静态对象出来,不管之后会不会使用这个单例,都会占据一定的内存,但是相应的,
在第一次调用时速度也会更快,因为其资源已经初始化完成。
懒汉模式则会延迟加载,第一次使用的时候才会实例化对象,第一次调用时要做初始化,性能上会有些延迟,之后就和饿汉式一样了。
总结:
下面有两个问题需要注意:
1、如果单例由不同的类装载器装入,那便有可能存在多个单例类的实例。
假定不是远端存取,例如一些servlet容器对每个servlet使用完全不同的类装载器,这样的话如果有两个servlet访问一个单例类,它们就都会有各自的实例。
2、如果Singleton实现了java.io.Serializable接口,那么这个类的实例就可能被序列化和复原。
不管怎样,如果你序列化一个单例类的对象,接下来复原多个单例类对象,那你就会有多个单例类的实例。
对问题1修复的办法是:
private static Class getClass(String classname) throws ClassNotFoundException {ClassLoader classLoader = Thread.currentThread().getContextClassLoader();
if(classLoader == null)
classLoader = Singleton.class.getClassLoader();return (classLoader.loadClass(classname));
}
}对问题2修复的办法是:
public class Singleton implements java.io.Serializable {
public static Singleton INSTANCE = new Singleton();
protected Singleton() {
}
private Object readResolve() {
return INSTANCE;
}
}
最后,如果大家有什么不同的意见或者更好的看法欢迎提出来!