java设计模式1.单例模式

设计模式分类:java

  • 建立型模式:工厂模式、单例模式、建造者模式、原型模式。
  • 结构型模式:适配器模式、组合模式、装饰器模式、代理模式、门面模式、桥接模式、享元模式。
  • 行为型模式:策略模式、模板方法模式、观察者模式、迭代子模式、责任链模式、命令模式、备忘录模式、访问者模式、调停者模式等。

设计模式原则:
开闭原则:对扩展开放,对修改封闭。在程序须要进行拓展的时候,不能去修改原有的代码,而是要扩展原有代码,实现一个热插拔的效果。因此一句话归纳就是:为了使程序的扩展性好,易于维护和升级,想要达到这样的效果,咱们须要使用接口和抽象类等,后面的具体设计中咱们会提到这点。编程

  • 1.单一职责原则

不要存在多于一个致使类变动的缘由,也就是说每一个类应该实现单一的职责,不然就应该把类拆分。设计模式

  • 2.里氏替换原则

任何基类能够出现的地方,子类必定能够出现。里氏替换原则是继承复用的基石,只有当衍生类能够替换基类,软件单位的功能不受到影响时,基类才能真正被复用,而衍生类也可以在基类的基础上增长新的行为。安全

里氏代换原则是对“开-闭”原则的补充。实现“开闭”原则的关键步骤就是抽象化。而基类与子类的继承关系就是抽象化的具体实现,因此里氏代换原则是对实现抽象化的具体步骤的规范。里氏替换原则中,子类对父类的方法尽可能不要重写和重载。由于父类表明了定义好的结构,经过这个规范的接口与外界交互,子类不该该随便破坏它。并发

  • 3.依赖倒转原则

面向接口编程,依赖于抽象而不依赖于具体。写代码时用到具体类时,不与具体类交互,而与具体类的上层接口交互。app

  • 4.接口隔离原则

每一个接口中不存在子类用不到却必须实现的方法,若是否则,就要将接口拆分。使用多个隔离的接口,比使用单个接口(多个接口方法集合到一个的接口)要好。性能

  • 5.迪米特法则(最少知道原则)

一个类对本身依赖的类知道的越少越好。不管被依赖的类多么复杂,都应该将逻辑封装在方法的内部,经过public方法提供给外部。这样当被依赖的类变化时,才能最小的影响该类。
最少知道原则的另外一个表达方式是:只与直接的朋友通讯。类之间只要有耦合关系,就叫朋友关系。耦合分为依赖、关联、聚合、组合等。咱们称出现为成员变量、方法参数、方法返回值中的类为直接朋友。局部变量、临时变量则不是直接的朋友。咱们要求陌生的类不要做为局部变量出如今类中。优化

  • 6.合成复用原则

尽可能首先使用合成/聚合的方式,而不是使用继承。

spa

  • 单例模式

 要点:线程

  • 1.只能有一个类实例;
  • 2.类必须自行建立这个实例;
  • 3.类必须自行向整个系统提供这个实例。

常见实现方式:饿汉式、延迟加载、.延长初始化占位类模式、枚举方式。

  • 1. 饿汉式
public class Singleton {   
    private static Singleton = new Singleton();
    private Singleton() {}
    public static getSignleton(){
        return singleton;
    }
}

java初始器中采用了特殊的方式来处理静态域,并提供了额外的线程安全性保证。静态初始化器是由JVM在类的初始化阶段执行,即在类被加载后且被线程使用以前。因为JVM将在初始化期间得到一个锁,而且每一个线程都至少获取一次这个锁以确保这个类已经加载,所以在静态初始化期间,内存写入操做将自动对全部线程可见。所以不管是在被构造期间仍是被引用时,静态初始化的对象都不须要显示的同步。然而,这仅适用于在构造时的状态,若是对象是可变的,那么在读线程和写线程之间仍然须要经过同步来确保随后的修改操做是可见的,以免数据破坏。

这种在第一次引用该类的时候就建立对象实例,无论实际是否须要,其缺点在于该类加载的时候就会直接new 一个静态对象出来,当系统中这样的类较多时,会使得启动速度变慢 ,因此比较适合小系统。

 

  • 2. 延迟加载(非线程安全)
public class Singleton {
    private static Singleton singleton = null;
    private Singleton(){}
    public static Singleton getSingleton() {
        if(singleton == null) 
           singleton = new Singleton();
        return singleton;
    }
}

写法很简单,由私有构造器和一个公有静态工厂方法构成,在工厂方法中对singleton进行null判断,若是是null就new一个出来,最后返回singleton对象。可是这样有一个致命弱点:若是有两条线程同时调用getSingleton()方法,就有很大可能致使重复建立对象。

 

  • 3. 延迟加载(线程安全)
public class Singleton {
    private static Singleton singleton;
    private Singleton(){}
    public synchronized static Singleton getSingleton(){
        if(singleton == null){
            singleton = new Singleton();
        }
        return singleton;
    }    
}

考虑线程安全,对singleton的null判断以及new的部分使用synchronized进行加锁。因为getSingleton的代码路径很短,所以若是getSingleton没有被多个线程频繁调用。那么不会存在激烈的竞争。可是早期JVM在性能上存在一些有待优化的地方(1.5及以前),同步哪怕没有竞争的同步都存在着巨大的性能开销。

 

  • 4. 双重检查锁
public class Singleton {
 
    private static volatile Singleton singleton;
 
    private Singleton(){
 
    }
 
    public  static Singleton getSingleton(){
        if(singleton != null){
            return singleton;
        }
 
        synchronized(Singleton.class){
            if(singleton == null){
               singleton = new Singleton();
            }
        }
        return singleton;
    }    
}

因而对上一种方式进行改进,进行双重检查,由于在大多数状况是获取一个已经构造好的Resource引用,因此代码路径并不会涉及到同步块,因此就避免了在同步上的消耗。可是又因为对于实例状态的第一次判断访问没有使用同步,将会存在可见性问题,一样是不安全的,所以须要使用volatile来保证初始化实例的可见性。

 

  • 5. 延长初始化占位类模式
public class Singleton {
    private static class Holder {
        private static Singleton singleton = new Singleton();
    }
 
    private Singleton(){
    
    }
 
    public static Singleton getSingleton(){
        return Holder.singleton;
    }
}

使用一个专门的内部静态类来初始化Singleton,JVM将推迟Holder的初始化操做,直到开始使用这个类时才初始化,而且因为经过一个静态初始化来初始化Singleton,所以不须要额外的同步。当任何一个线程第一次调用getSingleton时,都会使Holder被加载和被初始化,此时静态初始化器将执行Singleton的初始化操做。其实就是将单例的初始化动做放在了一个被延迟了的类加载动做中,这个类加载动做保证了单例初始化的线程安全

可是,上面提到的全部实现方式都有两个共同的缺点:须要额外的工做(Serializable、transient、readResolve())来实现序列化,不然每次反序列化一个序列化的对象实例时都会建立一个新的实例。另外阻止不了使用反射强行调用私有构造器(若是要避免这种状况,能够修改构造器,让它在建立第二个实例的时候抛异常)。

 

  • 6. 枚举方式
public enum Singleton  {
    SPRING,SUMMER,AUTUMN,WINTER;
}

使用枚举除了线程安全和防止反射强行调用构造器以外,还提供了自动序列化机制,防止反序列化的时候建立新的对象。所以,Effective Java 也推荐使用枚举来实现单例。关于java枚举的介绍能够单独写一个篇章了,这里简单介绍下枚举类是如何作到线程安全以及防止反序列建立新对象的。

enum就和class同样,只是一个关键字,他并非一个类,那么枚举是由什么类维护的呢,可使用反编译进行查看:

public final class T extends Enum
{
    private T(String s, int i)
    {
        super(s, i);
    }
    public static T[] values()
    {
        T at[];
        int i;
        T at1[];
        System.arraycopy(at = ENUM$VALUES, 0, at1 = new T[i = at.length], 0, i);
        return at1;
    }
 
    public static T valueOf(String s)
    {
        return (T)Enum.valueOf(demo/T, s);
    }
 
    public static final T SPRING;
    public static final T SUMMER;
    public static final T AUTUMN;
    public static final T WINTER;
    private static final T ENUM$VALUES[];
    static
    {
        SPRING = new T("SPRING", 0);
        SUMMER = new T("SUMMER", 1);
        AUTUMN = new T("AUTUMN", 2);
        WINTER = new T("WINTER", 3);
        ENUM$VALUES = (new T[] {
            SPRING, SUMMER, AUTUMN, WINTER
        });
    }
}

public final class T extends Enum,说明该类是继承了Enum类的,同时final关键字告诉咱们,这个类也是不能被继承的。当咱们使用enmu来定义一个枚举类型的时候,编译器会自动帮咱们建立一个final类型的类继承Enum类。它的属性和方法都是static类型的,所以是线程安全的。

另外,以前的单例模式都有一个比较大的问题,就是一旦实现了Serializable接口以后,就再也不是单例的了,由于,每次调用 readObject()方法返回的都是一个新建立出来的对象,有一种解决办法就是使用readResolve()方法来避免此事发生。可是,为了保证枚举类型像Java规范中所说的那样,每个枚举类型极其定义的枚举变量在JVM中都是惟一的,在枚举类型的序列化和反序列化上,Java作了特殊的规定。原文以下:

Enum constants are serialized differently than ordinary serializable or externalizable objects. The serialized form of an enum constant consists solely of its name; field values of the constant are not present in the form. To serialize an enum constant, ObjectOutputStream writes the value returned by the enum constant’s name method. To deserialize an enum constant, ObjectInputStream reads the constant name from the stream; the deserialized constant is then obtained by calling the java.lang.Enum.valueOf method, passing the constant’s enum type along with the received constant name as arguments. Like other serializable or externalizable objects, enum constants can function as the targets of back references appearing subsequently in the serialization stream. The process by which enum constants are serialized cannot be customized: any class-specific writeObject, readObject, readObjectNoData, writeReplace, and readResolve methods defined by enum types are ignored during serialization and deserialization. Similarly, any serialPersistentFields or serialVersionUID field declarations are also ignored–all enum types have a fixedserialVersionUID of 0L. Documenting serializable fields and data for enum types is unnecessary, since there is no variation in the type of data sent.

大概意思就是说,在序列化的时候Java仅仅是将枚举对象的name属性输出到结果中,反序列化的时候则是经过java.lang.Enum的valueOf方法来根据名字查找枚举对象。同时,编译器是不容许任何对这种序列化机制的定制的,所以禁用了writeObject、readObject、readObjectNoData、writeReplace和readResolve等方法。 咱们看一下这个valueOf方法:

public static <T extends Enum<T>> T valueOf(Class<T> enumType,String name) {  
            T result = enumType.enumConstantDirectory().get(name);  
            if (result != null)  
                return result;  
            if (name == null)  
                throw new NullPointerException("Name is null");  
            throw new IllegalArgumentException(  
                "No enum const " + enumType +"." + name);  
}  

从代码中能够看到,代码会尝试从调用enumType这个Class对象的enumConstantDirectory()方法返回的map中获取名字为name的枚举对象,若是不存在就会抛出异常。再进一步跟到enumConstantDirectory()方法,就会发现到最后会以反射的方式调用enumType这个类型的values()静态方法,也就是上面咱们看到的编译器为咱们建立的那个方法,而后用返回结果填充enumType这个Class对象中的enumConstantDirectory属性。

#笔记内容参考

1.《java并发编程实战》

2.《java与模式》

3. http://www.hollischuang.com/archives/197

相关文章
相关标签/搜索