一、什么是单例模式
单例模式是程序设计中常用到的一种设计模式,主要是使用在:我们只需要一个类实现保存一个对象,不希望有更多的对象被实例化保存从而造成不必要的资源浪费,这时候我们用到的设计模式就叫单例模式。
二、单例模式的特点
1、单例模式只有一个实例instance
2.、构造方法必须private私有,不能被其他new(包括子类)
private Singleton(){}
3、有静态static的类变量保存单例对象
private static Singleton instance = new Singleton();
4、单例对象的获取方式,一般采用getInstance
public static Singleton getInstance(){
return instance;
}
三、单例模式的Java代码简单实现
1、懒汉模式
所谓的懒汉模式(个人理解就是得到用到的时候再说),如果单例的实例已经被创建,则直接返回该实例,如果为null没有被创建则创建实例。
public class SingletonDemo {
private static SingletonDemo instance;
private SingletonDemo(){
}
public static SingletonDemo getInstance(){
if(instance==null){
//用到的时候才new实例化->懒汉
instance=new SingletonDemo();
}
return instance;
}
}
这种懒汉模式有很明显的lazy loading(延迟加载)问题。但是这里有个坑,如果多个访问者访问方法getInstance(),则会造成创建多个instance实例,这也就是没有考虑到多线程的安全的问题。为了解决线程不安全的问题,自然有加锁的线程安全的懒汉模式。
public class SingletonDemo {
private static SingletonDemo instance;
private SingletonDemo(){
}
public static synchronized SingletonDemo getInstance(){
if(instance==null){
instance=new SingletonDemo();
}
return instance;
}
}
但是我们都知道加了锁之后的效率会很低下,并发的情况下就显得不适用。
2、饿汉模式
饿汉模式,个人理解就是一上来就需要实例化,它没有lazy loading的效果,类加载时就实例化的结果是相当于多了个常驻的内存(不同于静态类方法),它的好处就是类记载即实例化可以避免线程安全的问题,但同时不能达到需要的时候用到的特定效果(不管用不用得到,实例化后的对象都存在),只有会造成一定的内存浪费,同时也没有lazy loading效果
public class SingletonDemo {
//加载时就实例化,之后直接访问->饿汉。
private static SingletonDemo instance=new SingletonDemo();
private SingletonDemo(){
}
public static SingletonDemo getInstance(){
return instance;
}
}
为了解决用到的时候才实例化,而不是类记载即实例化同时考虑到线程安全问题,我们可以再一步优化使用静态类内部加载实现。
3、静态类内部加载
静态内部就是弥补饿汉模式的缺点(先加载类之后有需要时实例化对象),在内部类中加载类,之后调用内部类方法实现实例化对象。同时也实现了lazy loading机制。
public class SingletonDemo {
private static class SingletonMethod{
//这里虽然类已经被加载,但是还未立即实例化
private static SingletonDemo instance=new SingletonDemo();
}
private SingletonDemo(){
}
public static SingletonDemo getInstance(){
//调用内部类SinglteonMethod的方法之后才会被实例化
return SingletonMethod.instance;
}
}
4、双重校验锁
加了锁之后的懒汉模式似乎看起来解决了“线程安全”与“延迟加载”两个问题,但是加了锁之后大大降低了性能,所以利用双重检验锁可以解决这问题
public class Singleton {
private static Singleton instance = null;
private Singleton(){}
public static Singleton getInstance() {
if (instance == null) {
synchronized (Singleton.class) { //1处
if (instance == null) {//2处
instance = new Singleton();//3处
}
}
}
return instance;
}
}
下面来详细了解它是怎样的实现原理。
第一步:线程A访问getInstance()方法,由于单例没有被实例化,所以进入了1处锁定块。
第二步:线程B也访问getInstance()方法,同样由于单例没有被实例化,也同样进进入1处锁定块,想获取锁,但是已经被线程A获取。线程B在1处阻塞。
第三步:线程A继续进入2处,由于为null,就继续进入3处实例化对象instance。
第四步:线程A退出锁synchronized块同时return instance,此时单例已经被实例化。
第五步:此时线程B获得锁,判断instance是否被实例化,是否为null,而线程A已经实例化对象instance,所以此时跳出3处,直接返回被线程A实例化了的instance。
这种方式看似完美解决了问题,但是很遗憾由于java中无序写入(具体解释),该方法并不完美,而且可能会犯很大的错误。
注:关于Java的内存模型“无序写入”问题,我的理解就是对象实例化的时候,如Person p = new Person(),正常流程是:实例p被创造之后会继续执行初始化构造函数new Person(),最后将p指向分配的内存空间(p此时不再为null),从而完成初始化。但是期间可能p实例在构造函数初始化之前就已经不为null(而不是最后被指向空间),也就是实例化p指向内存空间与构造函数初始化两个步骤没有固定的执行顺序,这就是“无序”问题。
四、单例模式的优缺点及使场景
优点:
(1)只有一个对象,减少了内存的使用。
(2)对外界提供了唯一的实例化方法接口,保持了其内部的完整性外部的简单性。
(3)避免对共享资源多重占用,大大提高了效率。
(4)可以满足特定的实例化情况。
(5)相比较全局变量/静态类方法来说,使用单例模式可以lazy loading延迟初始化。缺点:
(1)单例模式可扩展性差,这是由于并没有存在抽象层的东西让其扩展。
(2)单例模式使用的时候如果不注意线程安全的问题,将会影响读/写共享数据。
(3)在并行开发测试的时候,单例模式可能会影响测试。单例模式使用场景:
(1)单例模式只允许创建一个对象,所以常常用于被共用资源的场景下,比如JDBC中多个访问者使用一个数据库连接池Connection连接。
(2)频繁创建对象并且消耗的资源过多的情况下可以使用单例模式。
(3)类似于网站计数器,都是采用单例模式。