zoukankan      html  css  js  c++  java
  • 设计模式-单例模式

    单例,顾名思义全局只能有一个实例对象。

    基本原理就是

      1. 使构造方法私有化(不能随便的通过构造方法创建实例来保证单例);

      2. 有一个可以获取实例的静态方法(因为自己不可以创建实例,所以该方法必须是静态的,否则无法调用);

      3. 有一个静态私有的局部变量指向自己(私有是因为不可外部调用,静态是因为2中的静态方法需要引用)。

    单例模式又有饿汉式和懒汉式之分

      按名字理解,饿汉式比较急,不管用没用到直接先创建一个实例,好处是快,在需要的时候直接用,坏处是占用资源,因为如果没有用到,它也创建好在那放着:

     1 //单例模式-饿汉式-线程安全
     2 public class Singleton{
     3     //静态私有的全局变量
     4     private static Singleton singleton = new Singleton();
     5     //构造方法私有化,不允许外部创建
     6     private Singleton(){}
     7     //获取实例的静态方法,直接调用即可
     8     public static Singleton getInstance(){
     9         return singleton;
    10     }
    11 }

      懒汉式比较懒,等到用的时候才会创建出一个实例,好处是不会浪费资源,坏处是初次调用有一个创建的时间:

     1 //单例模式-懒汉式-非线程安全
     2 public class Singleton{
     3     //静态私有的全局变量
     4     private static Singleton singleton;
     5     //构造方法私有化,不允许外部创建
     6     private Singleton(){}
     7     //获取实例的静态方法,直接调用即可
     8     public static Singleton getInstance(){
     9         //判断实例是否为空,如果为空也就是第一次调用则创建一个实例对象
    10         if(singleton == null){
    11             singleton = new Singleton();
    12         }
    13         return singleton;
    14     }
    15 }

      饿汉式是线程安全的,因为它是在类加载时直接初始化了一个实例,多个线程同时调用也只是这一个实例;而懒汉式则是非线程安全的,因为它是调用的时候才会创建实例,这样就有一个问题,当多个线程假如同时到了判断为空的逻辑时,就会每个线程创建一个实例,这样就违背了单例的规则。

      为了保证线程安全我们可以使用synchronized关键字进行方法同步或代码块,建议只同步判断为空的代码块,因为如果同步方法同步的作用域会比较大,锁的粒度比较粗,效率会低。下面同步方法的代码已经注释掉,保留了同步代码块的形式,这样即可保证线程安全。

     1 //单例模式-懒汉式-线程安全
     2 public class Singleton{
     3     //静态私有的全局变量
     4     private static Singleton singleton;
     5     //构造方法私有化,不允许外部创建
     6     private Singleton(){}
     7     //获取实例的静态方法,直接调用即可
     8     /*public static synchronized Singleton getInstance(){
     9         //判断实例是否为空,如果为空也就是第一次调用则创建一个实例对象
    10         if(singleton == null){
    11             singleton = new Singleton();
    12         }
    13         return singleton;
    14     }*/
    15 
    16     //获取实例的静态方法,直接调用即可
    17     public static Singleton getInstance(){
    18         synchronized (Singleton.class) {
    19             //判断实例是否为空,如果为空也就是第一次调用则创建一个实例对象
    20             if (singleton == null) {
    21                 singleton = new Singleton();
    22             }
    23         }
    24         return singleton;
    25     }
    26 }

       但这样因为有同步代码块效率其实还是低,我们可以使用内部类的方式实现:

     1 //单例模式-懒汉式-线程安全
     2 public class Singleton{
     3     //私有内部类,需要时进行加载
     4     private static class Inner {
     5         private static Singleton singleton = new Singleton();
     6     }
     7     //构造方法私有化,不允许外部创建
     8     private Singleton(){}
     9     //获取实例的静态方法,直接调用即可
    10     public static Singleton getInstance(){
    11         return Inner.singleton;
    12     }
    13 }

       这样既保证了效率(因为是在使用时才进行的实例初始化),也保证了线程安全问题(同饿汉式相似,是在内部类加载时直接进行的初始化)。

      还有一种,使用双重检查的方式创建单例,也可以提高运行效率:

     1 //单例模式-懒汉式-双重检查-线程安全
     2 public class Singleton{
     3     //静态私有的局部变量,使用volatile关键字防止重排序,因为new Singleton()的操作不是原子的,可能会创建一个不完整的实例
     4     private static volatile Singleton singleton;
     5     //构造方法私有化,不允许外部创建
     6     private Singleton(){}
     7     //获取实例的静态方法,直接调用即可
     8     public static Singleton getInstance(){
     9         //Double-Check
    10         if(singleton == null){
    11             synchronized (Singleton.class){
    12                 //只需要在第一次创建实例时进行同步
    13                 if(singleton == null){
    14                     singleton = new Singleton();
    15                 }
    16             }
    17         }
    18         return singleton;
    19     }
    20 }

      如上所示,为了保证单例的条件下提高效率,我们对唯一实例进行了二次检查,这样只有第一次创建实例时会进入同步块,但是需要注意,这种方式必须使用volatile修饰实例。

      因为创建实例的过程不是原子的(1.分配内存空间;2.初始化对象;3.使实例指向刚分配的内存地址),这个过程可能发生无序写入(改变指令顺序),假如有两个线程,第一个首次调用无疑会创建实例,在创建实例的时候指令顺序变成了1 3 2,在执行完第三步而没有初始化对象的时候,另一个线程到了第一个判断为空的逻辑,这时候实例不为空会直接返回,但是也没有进行初始化,这样就会导致程序出现问题。而volatile关键字正好可以解决这个问题。

      总结以上实现单例模式一共六种:

      1. 传统饿汉式,线程安全;

      2. 传统懒汉式,非线程安全;

      3. 使用synchronized关键字修饰方法的懒汉式,线程安全;

      4. 使用synchronized关键字修饰代码块的懒汉式,线程安全;

      5. 使用内部类延迟加载的懒汉式,线程安全;

      6. 使用双重检查的懒汉式,线程安全;

      到这里单例模式基本已经介绍完,但是需要注意的是:单例模式如果使用反射的方式依然是可以创建多个实例的,因为反射可以拿到私有的构造方法,就可以自己创造实例,所以我们使用单例模式要禁止使用反射进行对象的创建。

  • 相关阅读:
    标准JSF的生命周期
    JSON
    Applet
    关于AJAX
    Java EE第十一周
    Java EE第八周
    Java EE第七周
    Java EE第六周
    Java EE第五周
    Java EE第四周
  • 原文地址:https://www.cnblogs.com/Qsunshine/p/10775903.html
Copyright © 2011-2022 走看看