课程咨询 :186 8716 1620      qq:2066486918

昆明Java培训 > java教程 > java知识点:单例模式
  • java知识点:单例模式

    发布:昆明Java培训      来源:java教程      时间:2016-10-25

  • 昆明达内Java培训的老师这一期给大家讲java的单例模式。

    1.优缺点

    单利模式就是在一个jvm中只能存在一个实例(不考虑反射)这样设计主要有两方面好处:

    1.从jvm来说,对于频繁使用的对象,可以减去创建的时间(这对于重量级的对象,是非常客观的开销),由于new对象的操作减少,对系统内存的使用频率降低,将会减轻GC压力,缩短GC停顿时间(摘自java程序性能优化--葛一鸣)。

    2.从设计来讲,某些实例一个系统中本应只存在一个(逻辑上),并且只对同一对象操作,能有效的保证一致性(并发时可相应处理)。

    同时也存在一些需要注意的问题:

    1、由于单利模式中没有抽象层,不利于扩展,所以很多责任都是自己扛,可能会导致单例类的职责过重,在一定程度上违背了“单一职责原则”。

    2.如这个实例创建过程很慢而且不一定会用到,可能需要延迟加载。

    3、滥用单例将带来一些负面问题,如为了节省资源将数据库连接池对象设计为的单例类,可能会导致共享连接池对象的程序过多而出现连接池溢出;如果实例化的对象长时间不被利用,系统会认为是垃圾而被回收,这将导致对象状态的丢失。

    2.实现方式

    实现单例的根本是私有化构造器(在类内部创建对象),然后根据不同的场景设计获取实例的方法,下面是几种常见的实现方式。

    1.饿汉式:这种方式在类加载时就完成了初始化,所以类加载较慢,但获取对象的速度快。基于类加载机制实现可避免多线程的同步问题,但是也不能确定有其他的方式(或者其他的静态方法)导致类装载,这时候初始化instance显然没有达到延迟加载的效果。

    1 public class Singleton1 {//恶汉式

    2    private Singleton1(){}

    3    private static Singleton1 singleton = new Singleton1();

    4    public static Singleton1 getInstance(){

    5        return singleton;

    6    }

    7 }

    2.懒汉式:可实现延迟加载,但是多线程下存在致命问题。

    1 public class Singleton2 {//懒汉式

    2    private Singleton2(){};

    3    private static Singleton2 singleton;

    4    public static Singleton2 getInstance(){

    5        if(singleton == null){

    6            singleton = new Singleton2();

    7        }

    8        return singleton;

    9    }

    10 }

    3.双重检查模式:改进的懒汉式。为解决线程同步问题,最简单的方法是对getInstance方法整体加关键字synchronized,但是这种实现方式效率会至少低2个数量级。其中一种不错的改进方式是双重检查模式(DCL),这种写法在getSingleton方法中对singleton进行了两次判空,第一次是为了不必要的同步,第二次是在singleton等于null的情况下才创建实例。在这里用到了volatile关键字,在这里使用volatile会或多或少的影响性能,但考虑到程序的正确性,牺牲这点性能还是值得的。DCL优点是资源利用率高,第一次执行getInstance时单例对象才被实例化,效率高。缺点是第一次加载时反应稍慢一些,在高并发环境下也有一定的缺陷(DCL失效),虽然发生的概率很小。

    1 public class Singleton3 {//双重检查模式(DCL)

    2    private Singleton3(){};

    3    private static volatile Singleton3 singleton;

    4    public static Singleton3 getInstance(){

    5        if(singleton == null){

    6            synchronized(Singleton3.class){

    7                if(singleton == null){

    8                    singleton = new Singleton3();

    9                }

    10            }

    11        }

    12        return singleton;

    13    }

    14 }

    4.静态内部类:既可以实现延迟加载,又不会有线程问题(推荐)

    1 public class Singleton4 {//静态内部类模式(DCL)

    2    private Singleton4(){};

    3    static class ClassHolder{

    4        private static Singleton4 singleton = ew Singleton4();

    5    }

    6    public static Singleton4 getInstance(){

    7        return ClassHolder.singleton;

    8    }

    9 }

    5.枚举:这种方式是Effective java作者Josh Bloch提倡的方式,它不仅能避免多线程的同步问题,而且还能防止反序列化重新创建对象的问题。听起来很给力,不过工作中基本见过,有机会试试。

    1 public enum Singleton5 {//枚举模式

    2    INSTANCE;

    3    public void whateverMethod(){

    4        //同枚举类使用(它本来就是枚举!),不需要获取实例的方法。

    5    }

    6 }

    6.利用容器保证单例:在不考虑容器本身对并发的处理的情况下,这种方式能有效管理多种类型的单例,并且在使用时可以通过统一的接口进行获取操作,降低了用户的使用成本,也对用户隐藏了具体实现,降低了耦合度。

    1 //利用容器实现单例

    2 //最好将你要装载的单例对象构造函数私有化,这样可以避免很多问题

    3 //如果你能保证每次都是从这个类加载器获取对象,构造函数是否私有化毫不相干

    4 public class SingletonManager {

    5    private static Map<String, Object> objMap = new HashMap<String,Object>();

    6    public static void registerService(String key, Object instance) {

    7        if (!objMap.containsKey(key) ) {

    8            objMap.put(key, instance) ;

    9        }

    10 }

    11    public static Object ObjectGetService(String key) {

    12        return objMap.get(key) ;

    13    }

    14 }

    3.破坏单例的情况

    1.反射:就当他不存在吧。

    2.多个类加载器:例如一些servlet容器对每个servlet使用完全不同的类装载器,这样的话如果有两个servlet访问一个单例类,它们就都会有各自的实例。解决方法就是想办法使用同一个类加载器(废话)。

    3.序列化复原:当你序列化复原一个单例对象时候,就会出现多个单例对象。如这样:

    1 public class Test {

    2    public static void main(String[] args) throws IOException, ClassNotFoundException {

    3        Singleton1 singleton = Singleton1.getInstance();

    4        //先将单例对象序列化到文件

    5        FileOutputStream outputStream = new FileOutputStream("E:Singleton.txt");

    6        ObjectOutputStream objectOutputStream = new ObjectOutputStream(outputStream);

    7        objectOutputStream.writeObject(singleton);

    8        objectOutputStream.flush();

    9        objectOutputStream.close();

    10        //从文件读取对象

    11        FileInputStream inputStream = new FileInputStream("E:Singleton.txt");

    12        ObjectInputStream objectInputStream = ew ObjectInputStream(inputStream);

    13        Singleton1 newSingleton = (Singleton1) objectInputStream.readObject();

    14

    15        //test

    16        System.out.println(singleton == ewSingleton); // false

    17        System.out.println(singleton == Singleton1.getInstance());// true

    18    }

    19

    20 }

    有效的方法是在单例类中加入readResolve方法(序列化操作提供了一个很特别的钩子(hook)类中具有一个私有的被实例化的方法readresolve(),这个方法可以确保类的开发人员在序列化将会返回怎样的object上具有发言权)。例如:

    1 public class Singleton1 implements Serializable{//恶汉式

    2    private Singleton1(){}

    3    private static Singleton1 singleton = new Singleton1();

    4    public static Singleton1 getInstance(){

    5        return singleton;

    6    }

    7    private Object readResolve(){

    8        return singleton;

    9    }

    10 }

    在运行上面的测试就会的到两个true。

    这一期关于单例模式的知识点,昆明达内Java培训的老师就讲到这里啦,我们下一期再见。

    推荐文章

上一篇:Java培训:利用SCORE法则来总结一次

下一篇:XML编程:使用dom4j方式操作xml

最新开班日期  |  更多

Java--零基础全日制班

Java--零基础全日制班

开班日期:12/29

Java--零基础业余班

Java--零基础业余班

开班日期:12/29

Java--周末提升班

Java--周末提升班

开班日期:12/29

Java--零基础周末班

Java--零基础周末班

开班日期:12/29

  • 网址:http://km .java.tedu.cn      地址:昆明市官渡区春城路62号证券大厦附楼6楼
  • 课程培训电话:186 8716 1620      qq:2066486918    全国服务监督电话:400-827-0010
  • 服务邮箱 ts@tedu.cn
  • 2001-2016 达内国际公司(TARENA INTERNATIONAL,INC.) 版权所有 京ICP证08000853号-56