zoukankan      html  css  js  c++  java
  • Java设计模式03:常用设计模式之单例模式(创建型模式)

    1.  Java之单例模式(Singleton Pattern )

    单例模式是一种常见的设计模式,单例模式分三种:懒汉式单例饿汉式单例登记式单例三种。

      单例模式有一下特点:
      1、单例类只能有一个实例
      2、单例类必须自己自己创建自己的唯一实例
      3、单例类必须给所有其他对象提供这一实例

      单例模式确保某个类只有一个实例,而且自行实例化并向整个系统提供这个实例。在计算机系统中,线程池、缓存、日志对象、对话框、打印机、显卡的驱动程序对象常被设计成单例。这些应用都或多或少具有资源管理器的功能。每台计算机可以有若干个打印机,但只能有一个Printer Spooler,以避免两个打印作业同时输出到打印机中。每台计算机可以有若干通信端口,系统应当集中管理这些通信端口,以避免一个通信端口同时被两个请求同时调用。总之,选择单例模式就是为了避免不一致状态,避免政出多头

      正是由于这个特点,单例对象通常作为程序中的存放配置信息的载体,因为它能保证其他对象读到一致的信息。例如在某个服务器程序中,该服务器的配置信息可能存放在数据库或 文件中,这些配置数据由某个单例对象统一读取,服务进程中的其他对象如果要获取这些配置信息,只需访问该单例对象即可。这种方式极大地简化了在复杂环境 下,尤其是多线程环境下的配置管理,但是随着应用场景的不同,也可能带来一些同步问题。

    2.Java单例模式3种写法

    (1)懒汉:(用的时候,才去创建对象)

     1 public class Singleton {  
     2     private static Singleton instance;  
     3     private Singleton (){}   
     4     public static Singleton getInstance() {  
     5     if (instance == null) {  
     6         instance = new Singleton();  
     7     }  
     8     return instance;  
     9     }  
    10 }  
    11 

    致命的是在多线程不能正常工作,线程不安全

    以上懒汉式单例的实现没有考虑线程安全问题,它是线程不安全的,并发环境下很可能出现多个Singleton实例,要实现线程安全,对getInstance这个方法改造有以下三种方式,都是对getInstance这个方法改造,保证了懒汉式单例的线程安全

    优化懒汉,实现线程安全。做法如下3种

            •在getInstance方法上加同步     

    public static synchronized Singleton getInstance() {
             if (single == null) {  
                 single = new Singleton();
             }  
            return single;
    }

           双重检查锁定

    public static synchronized Singleton getInstance() {
             if (single == null) {  
                 single = new Singleton();
             }  
            return single;
    }

             •静态内部类

    public class Singleton {    
        private static class LazyHolder {    
           private static final Singleton INSTANCE = new Singleton();    
        }    
        private Singleton (){}    
        public static final Singleton getInstance() {    
           return LazyHolder.INSTANCE;    
        }    
    }    

    附加:懒汉式Teacher类案例:

      •Teacher类:

     1 package cn.itcast_03;
     2 
     3 /*
     4  *         面试:懒汉式(可能会出问题的单例模式)
     5  *             A:懒加载(延迟加载)    
     6  *             B:线程安全问题
     7  *                 a:是否多线程环境    是
     8  *                 b:是否有共享数据    是
     9  *                 c:是否有多条语句操作共享数据     是
    10  */
    11 public class Teacher {
    12     private Teacher() {
    13     }
    14 
    15     private static Teacher t = null;
    16 
    17     public synchronized static Teacher getTeacher() {
    18         // t1,t2,t3
    19         if (t == null) {
    20             //t1,t2,t3
    21             t = new Teacher();
    22         }
    23         return t;
    24     }
    25 }

      •Teacher测试类:

     1 package cn.itcast_03;
     2 
     3 public class TeacherDemo {
     4     public static void main(String[] args) {
     5         Teacher t1 = Teacher.getTeacher();
     6         Teacher t2 = Teacher.getTeacher();
     7         System.out.println(t1 == t2);
     8         System.out.println(t1); // cn.itcast_03.Teacher@175078b
     9         System.out.println(t2);// cn.itcast_03.Teacher@175078b
    10     }
    11 }

    (2)饿汉:(类一加载就创建对象

    //饿汉式单例类.在类初始化时,已经自行实例化   
    public class Singleton1 {  
        private Singleton1() {}  
        private static final Singleton1 single = new Singleton1();  
        //静态工厂方法   
        public static Singleton1 getInstance() {  
            return single;  
        }  
    }  

    饿汉式在类创建的同时就已经创建好一个静态的对象供系统使用,以后不再改变,所以天生是线程安全的

    其实饿汉还有变种编写方式如下:

     1 public class Singleton1 {  
     2     private Singleton1 instance = null;  
     3     static {  
     4     instance = new Singleton1();  
     5     }  
     6     private Singleton1 (){}
     7     public static Singleton1 getInstance() {  
     8     return this.instance;  
     9     }  
    10 }  
    11 

    表面上看起来差别挺大,其实更第三种方式差不多,都是在类初始化即实例化instance。

    (3)登记式单例(可忽略)

    //类似Spring里面的方法,将类名注册,下次从里面直接获取。  
    public class Singleton3 {  
        private static Map<String,Singleton3> map = new HashMap<String,Singleton3>();  
        static{  
            Singleton3 single = new Singleton3();  
            map.put(single.getClass().getName(), single);  
        }  
        //保护的默认构造子  
        protected Singleton3(){}  
        //静态工厂方法,返还此类惟一的实例  
        public static Singleton3 getInstance(String name) {  
            if(name == null) {  
                name = Singleton3.class.getName();  
                System.out.println("name == null"+"--->name="+name);  
            }  
            if(map.get(name) == null) {  
                try {  
                    map.put(name, (Singleton3) Class.forName(name).newInstance());  
                } catch (InstantiationException e) {  
                    e.printStackTrace();  
                } catch (IllegalAccessException e) {  
                    e.printStackTrace();  
                } catch (ClassNotFoundException e) {  
                    e.printStackTrace();  
                }  
            }  
            return map.get(name);  
        }  
        //一个示意性的商业方法  
        public String about() {      
            return "Hello, I am RegSingleton.";      
        }      
        public static void main(String[] args) {  
            Singleton3 single3 = Singleton3.getInstance(null);  
            System.out.println(single3.about());  
        }  
    }  

           

            登记式单例实际上维护了一组单例类的实例,将这些实例存放在一个Map(登记薄)中,对于已经登记过的实例,则从Map直接返回,对于没有登记的,则先登记,然后返回。 这里我对登记式单例标记了可忽略,我的理解来说,首先它用的比较少,另外其实内部实现还是用的饿汉式单例,因为其中的static方法块,它的单例在类被装载的时候就被实例化了。

     

    (4)枚举实现单例模式:

            单例模式约束一个类只能实例化一个对象。在Java中,为了强制只实例化一个对象,最好的方法是使用一个枚举量。这个优秀的思想直接源于Joshua Bloch的《Effective Java》(《Java高效编程指南》)。如果你的藏书室里还没有这本书,请搞一本,它是迄今为止最优秀的Java书籍之一。

      这里有几个原因关于为什么在Java中宁愿使用一个枚举量来实现单例模式:

           ♦ 自由序列化;

            保证只有一个实例(即使使用反射机制也无法多次实例化一个枚举量);

           ♦ 线程安全;

    案例:

     1 public enum AnimalHelperSingleton {
     2 
     3     INSTANCE;
     4 
     5     private AnimalHelperSingleton(){
     6 
     7     }
     8 
     9     public Animal[] buildAnimalList(){
    10         final Animal[] animals = new Animal[10];
    11 
    12         animals[0] = new SimpleAnimal(Animal.AnimalClass.MAMMAL, 
    13                 "Dog", true, Color.GRAY);
    14         animals[1] = new SimpleAnimal(Animal.AnimalClass.MAMMAL, 
    15                 "Cat", true, Color.YELLOW);
    16         animals[2] = new SimpleAnimal(Animal.AnimalClass.AMPHIBIAN,
    17                 "Frog", true, Color.GREEN);
    18         animals[3] = new SimpleAnimal(Animal.AnimalClass.BIRD,
    19                 "Crow", true, Color.BLACK);
    20         animals[4] = new SimpleAnimal(Animal.AnimalClass.BIRD,
    21                 "Cardinal", true, Color.RED);
    22         animals[5] = new SimpleAnimal(Animal.AnimalClass.ARTHROPOD,
    23                 "Mantis", false, Color.GREEN);
    24         animals[6] = new SimpleAnimal(Animal.AnimalClass.ARTHROPOD,
    25                 "Spider", false, Color.ORANGE);
    26         animals[7] = new SimpleAnimal(Animal.AnimalClass.MAMMAL, 
    27                 "Tiger", true, Color.ORANGE);
    28         animals[8] = new SimpleAnimal(Animal.AnimalClass.MAMMAL, 
    29                 "Bear", true, Color.BLACK);
    30         animals[9] = new SimpleAnimal(Animal.AnimalClass.BIRD, 
    31                 "Owl", true, Color.BLACK);
    32 
    33         return animals;
    34     }
    35 
    36 }

    如何使用:

    //Call singleton to build the animal list.
    Animal[] animals = AnimalHelperSingleton.INSTANCE.buildAnimalList();

    这种方式是Effective Java作者Josh Bloch 提倡的方式,它不仅能避免多线程同步问题,而且还能防止反序列化重新创建新的对象,可谓是很坚强的壁垒啊,不过,个人认为由于1.5中才加入enum特性,用这种方式写不免让人感觉生疏,在实际工作中,我也很少看见有人这么写过。

    3.小结:

    (1)饿汉式和懒汉式区别

    从名字上来说,饿汉和懒汉,

    饿汉就是类一旦加载,就把单例初始化完成,保证getInstance的时候,单例是已经存在的了,

    而懒汉比较懒,只有当调用getInstance的时候,才回去初始化这个单例。

    另外从以下两点再区分以下这两种方式:         

                   ->1、线程安全

                          饿汉式天生就是线程安全的,可以直接用于多线程而不会出现问题,

                     懒汉式本身是非线程安全的,为了实现线程安全有几种写法,分别是上面的1、2、3,这三种实现在资源加载和性能方面有些区别。

                   ->2、资源加载和性能

                              饿汉式在类创建的同时就实例化一个静态对象出来,不管之后会不会使用这个单例,都会占据一定的内存,但是相应的,在第一次调用时速度也会更快,因为其                   资源已经初始化完成,而懒汉式顾名思义,会延迟加载,在第一次使用该单例的时候才会实例化对象出来,第一次调用时要做初始化,如果要做的工作比较多,性能                   上会有些延迟,之后就和饿汉式一样了。

     

    (2)什么是线程安全

    如果你的代码所在的进程中有多个线程在同时运行,而这些线程可能会同时运行这段代码。如果每次运行结果和单线程运行的结果是一样的,而且其他的变量的值也和预期的是一样的,就是线程安全的。

    或者说:一个类或者程序所提供的接口对于线程来说是原子操作,或者多个线程之间的切换不会导致该接口的执行结果存在二义性,也就是说我们不用考虑同步的问题,那就是线程安全的。

    4. 面试题:单例模式的思想是什么?请写一个代码体现。

    答:单例模式的思想客户端不再需要考虑是否需要去实例化的问题,而把责任都给了应该负责的类去处理。保证一个类仅有一个实例,并提供一个访问它的全局访问点。

       开发:饿汉式(是不会出问题的单例模式)
       面试:懒汉式(可能会出问题的单例模式)
         A:懒加载(延迟加载)
         B线程安全问题
           a是否多线程环境   是
           b是否有共享数据   是
           c是否有多条语句操作共享数据   是

    代码实现:

     老师类:

     1 package cn.itcast_03;
     2 
     3 
     4 public class Teacher {
     5     private Teacher() {
     6     }
     7 
     8     private static Teacher t = null;
     9 
    10     public synchronized static Teacher getTeacher() {
    11         // t1,t2,t3
    12         if (t == null) {
    13             //t1,t2,t3
    14             t = new Teacher();
    15         }
    16         return t;
    17     }
    18 }

     测试类:

     1 package cn.itcast_03;
     2 
     3 public class TeacherDemo {
     4     public static void main(String[] args) {
     5         Teacher t1 = Teacher.getTeacher();
     6         Teacher t2 = Teacher.getTeacher();
     7         System.out.println(t1 == t2);
     8         System.out.println(t1); // cn.itcast_03.Teacher@175078b
     9         System.out.println(t2);// cn.itcast_03.Teacher@175078b
    10     }
    11 }
  • 相关阅读:
    C/C++&java communicate with each other 之 video file-streaming
    C/C++&java communicate with each other 之 video snapshot
    protobuf io 代码阅读
    利用逆波兰表达式,二叉树对sql语句解析
    cocos2d-x 添加sqlite3 时 报 lua_Number 错误
    error LNK2019: 无法解析的外部符号 _acosh,该符号在函数 _acoshFunc 中被引用
    visual studio 运行程序在副显示器上
    lua table 中#,getn,maxn 的区别
    'Cordova/CDVViewController.h' file not found
    [ISSUE]cannot run on the selected destination
  • 原文地址:https://www.cnblogs.com/hebao0514/p/4774841.html
Copyright © 2011-2022 走看看