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 }
原文地址:https://www.cnblogs.com/hebao0514/p/4774841.html