• 设计模式03------单例模式


    参考文献:《Java与模式》

    作为对象的创建模式,单例模式确保某一个类只有一个实例,而且自行实例化并向整个系统提供这个实例,这个类称为单例类。

    一. 单例模式的要点

    显然单例模式的要点有3个:

    • 某个类只能有一个实例
    • 它必须自行创建这个实例
    • 它必须自行向整个系统提供这个实例

    二. 单例模式分类

    1. 饿汉式单例类

    它是在Java语言里实现起来最为简便的单例类。

    public class EagerSingleton {
        
        private static final EagerSingleton instance=new EagerSingleton();
        
        /**
         * 私有的默认构造器
         */
        private EagerSingleton() {}
        
        /**
         * 静态工厂方法
         */
        public static EagerSingleton getInstance() {
            return instance;
        }
    
    }

    说明:这个类被加载时,静态变量instance会被初始化,此时类的私有构造器会被调用。这个时候,单例类的唯一实例就被创建出来了。

    因为构造器是私有的,因此该类是不可以被继承的。这种方式线程安全,但是会造成资源浪费,因为这个实例无论使用与否,一开始就会被创建出来。

    2. 懒汉式单例类

    与饿汉式单例类不同的是,懒汉式在第一次被引用时将自己实例化。如果加载器是静态的,那么在懒汉式类被加载时不会将自己实例化。

    public class LazySingleton {
        private static LazySingleton instance=null;
        /**
         * 私有构造器
         */
        private LazySingleton() {}
        /**
         * 静态工厂方法,返回此类的唯一实例
         */
        synchronized public static LazySingleton getInstance() {
            if(instance==null) {
                instance=new LazySingleton();
            }
            return instance;
        }
    
    }

    说明:在上面给出的懒汉式单例类中对静态工厂使用了同步化,以处理多线程环境。有些设计师在这里建议使用所谓的”双重检查成例“。必须指出的是,”双重检查成例“不可以在Java语言中使用。

    饿汉式相比懒汉式,它的资源利用效率差一些,但是速度和反应时间上却很快。懒汉式在实例化时,必须处理好在多个线程同时首次引用此类时的访问限制问题,特别时当单例类作为资源控制器在实例化时必然涉及资源初始化,而资源初始化很可能耗费时间。这意味着出现多线程同时首次引用此类的几率变得较大。

    这种方式如果不加syncharonized线程不安全。

    3.登记式单例类

    这个类是为了克服前两种不可以被继承的缺点而设计的。

    public class RegSingleton {
        static private HashMap registry=new HashMap();
        static {
            RegSingleton instance=new RegSingleton();
            registry.put(instance.getClass().getName(), instance);
        }
        /**
         * 保护的默认构造器
         */
        protected RegSingleton() {}
        /**
         * 静态工厂方法,返回此类的唯一的实例
         */
        public static RegSingleton getInstance(String name) {
            if(name==null) {//key is null
                name="com.test.b.RegSingleton";
            }
            if(registry.get(name)==null) {//value is null
                try {
                    registry.put(name, Class.forName(name).newInstance());//利用的反射机制,创造一个单利对象加入registry
                }catch (Exception e) {
                    System.out.println("Error happend");
                }
            }
            return (RegSingleton) registry.get(name);
        }
        
        /**
         * 一个示意性的商业方法
         */
        public String about() {
            return "Hello, I am RegSingleton";
        }
    
    }

    它的子类RegSingletonChild需要父类的帮助才能实例化。

    public class RegSingletonChild extends RegSingleton{
        public RegSingletonChild() {}
        /**
         * 静态工厂方法
         */
        public static RegSingletonChild getInstance() {
            return (RegSingletonChild) RegSingleton.getInstance("com.test.b.RegSingletonChild");
        }
    
        /**
         * 一个示意性的商业方法
         */
        public String about() {
            return "Hello, I am RegSingletonChild";
        }
    }

    缺点:父类的实例必须存在才可能有子类的实例

    4.“第三节的双重检查锁的单例模式”,这种模式会造成锁的开销,不好;因为synchronized加锁时,在线程数量比较多的情况下,如果CPU分配压力上升,则会导致大批线程阻塞,从而导致程序性能大幅度下降。因此,为了既能兼顾线程安全,又能提升程序性能,就采用内部类+懒汉模式的第五种方式。

    5.静态内部类+懒汉式(推荐)

     1 package singleton.inner;
     2 
     3 // 这种形式兼顾了饿汉式单例模式的内存浪费问题和synchronized的性能问题
     4 //完美的屏蔽了这两个缺点
     5 public class LazyInnerClass {
     6     private LazyInnerClass(){
     7 
     8     }
     9     public static final LazyInnerClass getInstance(){
    10         return MyInner.instance;
    11     }
    12 
    13     // 默认不加载
    14     private static class MyInner{
    15         private static final LazyInnerClass instance=new LazyInnerClass();
    16     }
    17 }
    View Code
     1 package singleton.inner;
     2 
     3 
     4 
     5 public class MyThread implements Runnable{
     6     @Override
     7     public void run() {
     8         LazyInnerClass instance1=LazyInnerClass.getInstance();
     9         System.out.println(Thread.currentThread().getName()+":"+instance1);
    10     }
    11 }
    View Code
     1 package singleton.inner;
     2 
     3 public class LazyInnerTest {
     4     public static void main(String[] args) {
     5         Thread t1=new Thread(new MyThread());
     6         Thread t2=new Thread(new MyThread());
     7         t1.start();
     8         t2.start();
     9 
    10     }
    11 }
    View Code

    运行后,始终只有一个实例,实现单例。

    三. 双重检查成例的研究

    双重检查成例(Double Check Idiom)是从C语言移植过来的一种代码模式。具体分析如下:

    1. 未使用任何线程安全考虑的错误例子

    首先考虑一个单线程的版本,代码如下:

    public class Foo {
        private Helper helper=null;
        public Helper getHelper() {
            if(helper==null) {
                helper=new Helper();
            }
            return helper;
        }
    }

    说明:这是一个错误的例子。写出这样的代码,本意显然是要保证在整个JVM中只有一个Helper的实例。因此,才会有if(helper==null)的检查。非常明显的是,如果在多线程的环境中运行,上面的代码会有两个甚至两个以上的helper对象被创建出来,从而造成错误。想象一下在多线程中的情况。假设两个线程A和B几乎同时到达if(helper==null)语句的外面,假设A稍微比B早一点点,则:

    2. 线程安全的版本

    public class Foo {
        private Helper helper=null;
        public synchronized Helper getHelper() {
            if(helper==null) {
                helper=new Helper();
            }
            return helper;
        }
    }

     

  • 相关阅读:
    BZOJ 1098[POI2007]办公楼
    BZOJ 3629[JLOI2014]聪明的燕姿
    BZOJ 1064[NOI2008]假面舞会
    BZOJ 2818GCD
    【五校联考6day2】san
    【五校联考6day2】er
    【五校联考6day2】yi
    【五校联考3day2】B
    【五校联考5day1】序列
    【五校联考3day2】A
  • 原文地址:https://www.cnblogs.com/Hermioner/p/10014136.html
Copyright © 2020-2023  润新知