单例模式(Singleton Pattern) -(最通俗易懂的案例)

单例设计模式介绍

单例模式属于创建型模式,所谓类的单例设计模式,就是采用一定的方法保证在整个的软件系统中,对某个类只能存在唯一的实例,并且该类只提供一个取得其对象实例的方法(静态方法)。
比如Hibernate的SessionFactory,它充当数据存储源的代理,并负责创建Session对象。SessionFactory并不是轻量级的,一般情况下,一个项目通常只需要一个SessionFactory就够了,这时就会使用到单例模式。

单例模式的几种实现方式

1.饿汉式(静态常量)

实现步骤:

  • 构造器私有化(防止new)
  • 类的内部创建对象
  • 向外部暴露一个静态的公共方法
  • 代码实现
public class SingletonTest01 {
    public static void main(String[] args) {
        Singleton instance = Singleton.getInstance();
        Singleton instance1 = Singleton.getInstance();
        System.out.println(instance==instance1);
    }
}

class Singleton {

    //1.设置私有构造方法,这就堵死了外界利用new关键字创建此类实例的可能
    private Singleton(){

    }

    //2.本类内部创建对象实例
    private static final Singleton SINGLETON = new Singleton();


    //3.对外提供一个公有的静态方法,返回实例对象
    public static Singleton getInstance(){
        return SINGLETON;
    }


}

打印输出:

true

优缺点分析:
优点:这种写法比较简单,就是在类装载的时候就完成了实例化。避免了线程同步问题,即是线程安全的。

缺点:在类装载的时候就完成实例化,没有达到Lazy Loading的效果。若从始至终从未使用过这个实例,则会造成内存的浪费

结论:这种单例模式可用,但可能造成内存浪费。

其实在JDK中也有用这种方式实现单例模式:在java.lang包下的Runtime类就是用了这种方法,源码如下:

public class Runtime {
    private static Runtime currentRuntime = new Runtime();

    /**
     * Returns the runtime object associated with the current Java application.
     * Most of the methods of class <code>Runtime</code> are instance
     * methods and must be invoked with respect to the current runtime object.
     *
     * @return  the <code>Runtime</code> object associated with the current
     *          Java application.
     */
    public static Runtime getRuntime() {
        return currentRuntime;
    }

    /** Don't let anyone else instantiate this class */
    private Runtime() {}
    
}

2.懒汉式(线程不安全)

上面我们说到通过饿汉式来实现单例模式有一个缺点,就是在类加载的时候就被实例化了,没有达到懒加载的效果,若该实例一直未被使用就造成了资源的浪费。那有没有办法解决这个问题呢?
接下来我们看一个懒汉式实现单例模式,直接上代码:

    public class SingletonTest02 {
        public static void main(String[] args) {
            System.out.println("懒汉式,此种方法线程不安全");
            Singleton singleton = Singleton.getInstance();
            Singleton singleton1 = Singleton.getInstance();
            System.out.println(singleton == singleton1);
    
        }
    }
    
    class Singleton {
    
        private static Singleton singleton;
    
        private Singleton() {
        }
    
        public static Singleton getInstance() {
            if (singleton == null) {
                singleton = new Singleton();
            }
            return singleton;
        }
    } 

打印输出:

  懒汉式,线程不安全
  true

我们在Singleton类中定义一个静态变量但并不直接实例化他,同样也需要将构造方法私有化以防止其被他人实例化,随后提供一个获得其类的实例化的方法getInstance(),在该方法中先判断该静态变量singleton是否为空,若为空则实例化一个对象并返回,若不为空就直接将已经实例化的对象返回。

分析:此种方法对上一种方法进行了改进,实现了懒加载:在该类加载时并未创建该类的对象实例,而是等到用到该实例时再去调用该类对外提供的获取其实例化对象的方法getInstance()来获取该类的对象,也就实现了懒加载。
但是此种方式在多线程中是不安全的,分析:假设有两个线程thread1和thread2,由于是并发执行当两个线程一先一后分别调用了Singleton的getInstance()方法来获取实例,假设thread1先进入判断if (singleton == null),此时假设singleton未被实例化,thread1在进行判断时为true,进入方法区,但还没有进行对象的实例化,而此时thread2也来进入判断,由于thread1还没有执行到对象实例化的代码,故thread2判断也为true,所以也进入了方法区,随后thread1先进行对象singleton的创建,而thread2由于也进来了所以thread2也创建了一个该类的实例化对象,这就造成了线程不安全的情况。

优缺点分析:
优点:起到了Lazy Loading的效果,到只能在单线程下使用
缺点:如果在多线程下,一个线程进入了if(singleton==null)进行判断,还未来得及往下执行,另一个线程也通过了这个判断语句,这时变回产生多个实例。所以在多线程下不可以使用这种方式。

3.懒汉式(线程安全)

对上一个懒汉式进行改进:使用synchronized关键字来修饰getInstance()方法,作用就是该方法只能一个线程来访问也就实现了线程安全了;上代码:

  public class SingletonTest03 {
        public static void main(String[] args) {
            System.out.println("懒汉式,线程不安全");
            Singleton singleton = Singleton.getInstance();
            Singleton singleton1 = Singleton.getInstance();
            System.out.println(singleton==singleton1);
        }
    }
    
    class Singleton {
    
        private static Singleton singleton;
    
        private Singleton() {
        }
    
        //加入同步处理的代码
        public static synchronized Singleton getInstance() {
            if (singleton == null) {
                singleton = new Singleton();
            }
            return singleton;
        }
    }

打印输出:

  懒汉式,线程不安全
  true

优缺点分析:
优点:解决了线程不安全的问题,也实现了懒加载
缺点:效率太低了,每个线程在想获得类的实例的时候,执行getInstance()方法都要进行同步

4.双重检查

先上代码再说明什么是双重检查:

  public class SingletonTest04 {
      public static void main(String[] args) {
          System.out.println("懒汉式,线程不安全");
          Singleton singleton = Singleton.getInstance();
          Singleton singleton1 = Singleton.getInstance();
          System.out.println(singleton==singleton1);
      }
  }
  
  class Singleton {
  
      private static volatile Singleton singleton;
  
      private Singleton() {
      }
  
      public static  Singleton getInstance() {
          if (singleton == null) {
              synchronized (Singleton.class){
                  if (singleton==null){
                      singleton = new Singleton();
                  }
              }
          }
          return singleton;
      }
  }

打印输出:

  双重检查,线程安全
  true

观察代码我们发现在getInstance()方法中有两次判断语句singleton == null,这也就是为什么称作为双重检查了。
通过上面几个例子,双重检查实现了懒加载我们已经理解了,那双重检查这种方式为什么是线程安全的呢?

分析:在多线程中,上面我们说过可能有多个线程先后来进行第一次的if语句singleton == null的判断(假设此时singleton未被实例化)并且也都进去了,但是进去之后发现里面的代码块被synchronized进行了修饰,也就意味着只能有一个线程进入,其他线程只能在外面等待,第一个进入的线程又要进行一次if语句判断,此时if判断为true,进入方法区并进行对象的实例化再返回,随后其他进程再进入synchronized代码块(当然是单个单个进入的)时,进行第二次的if语句进行判断,由于之前的线程已经创建了一个实例化对象,所以此次if语句判断为false,并直接将实例化过的对象返回,而新的线程(不是以上所说的那些线程)想要获得该类的实例化对象时候进行第一次if语句判断就返回false了(因为已经创建过了实例化对象),因此也就实现了线程安全。

分析:
Double-Check概念是多线程开发中经常使用到的,如代码中所示,我们今次那个了两次if(singleton==null)的检查,这样就保证了现成的安全性;同时也解决了懒加载问题,效率较高
在开发中推荐使用这种方式。

单例模式注意事项和细节说明

  • 单例模式保证了系统内存中该类只存在一个对象,节省了系统资源,对于一些需要频繁创建销毁的对象,使用单例模式可用提高系统性能
  • 当想实例化一个单例类的时候,必须要记住使用相应的获取对象的方法,而不是使用new
  • 单例模式使用的场景:
    • 需要频繁的进行创建和销毁的对象
    • 创建对象时耗时过多或耗费资源过多(即:重量级对象),但又经常使用到的对象,如:工具类对象、频繁访问数据库或文件的对象(比如数据源、session工厂等)
  • 1
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值