单例模式

概述

单例模式是比较简单的设计模式,使用也是非常的广泛。看一下关于单例模式的定义:

确保一个类只有一个实例,而且自行实例化并向整个系统提供这个实例。

它的定义包含了三个要点:

  • 一个类只有一个实例:外部程序无法通过new关键字来创建实例,构造方法用private修饰
  • 自行实例化:由类本身创建实例对象,为了外界可以访问这个实例,需要定义成静态的私有成员变量。
  • 提供实例: 因为无法提供new来床架对象,只能提供一个公有静态方法,返回创建的对象实例。

来看一下单例模式的通用代码:

饿汉式单例

class Singleton {
    private static final Singleton singletonTest = new Singleton();  //创建实例
    private Singleton() {}   //构造方法私有

    public static Singleton getInstance(){
        return singletonTest ;  //返回实例
    }
}

public class SingletonTest{
    public static void main(String[] args) {
        Singleton s1 = Singleton.getInstance() ;
        Singleton s2 = Singleton.getInstance();

        System.out.println(s1 == s2);  //true
    }
}

上面这种写法是最简单的写法,但是有一个问题,就是在类加载的时候就创建了实例,而不管是否需要创建这个实例,没有做到延迟加载 ,增加了负载。

懒汉式单例

为了解决上面的问题,对上面的通用代码进行如下改进,当系统需要该实例的时候,才产生实例对象,并返回,代码如下:

class LazySingleton{

    private static  LazySingleton  singleton = null ;

    private LazySingleton(){}

    public static LazySingleton getInstance(){
        if(singleton == null ){
            singleton = new LazySingleton() ;
        }
        return  singleton ;
    }
}

通过上面的代码发现,在类加载的时候并没有进行类的初始化操作,只有在第一次调用getInstance()方法时,才进行了实例化操作,实现了延迟加载的功能。那么问题又来了,啥问题问题,上面的类在单线程环境是没有问题的,但是在多线程环境中是不安全的无法保证实例的唯一性,那么在改进一下,看下面代码:

class LazySingleton{

    private static  LazySingleton  singleton = null ;

    private LazySingleton(){}

    public static synchronized LazySingleton getInstance(){
        if(singleton == null ){
            singleton = new LazySingleton() ;
        }
        return  singleton ;
    }
}

在getInstance的方法上添加了synchronized关键字,来保证线程之间的同步,确保实例的唯一性。看起来已经解决了上面的问题,但是新的问题其实又出现了,什么问题呢? 就是每次访问getInstance()方法时都进进行线程锁定的判断,在多线程高并发的环境下,使得系统的性能大大的降低了,那么有什么办法来解决这个问题吗? 当然有,看下面代码:

class LazySingleton{

    private static  LazySingleton  singleton = null ;

    private LazySingleton(){}

    public static LazySingleton getInstance(){
        if(singleton == null ){
            synchronized (LazySingleton.class){
                singleton = new LazySingleton() ;
            }

        }
        return  singleton ;
    }
}

最初为了保证线程之间的同步问题,给getInstance()的整个方法加了锁,其实要保证同步的罪魁祸首其实是 singleton=new LazySingleton()这句,只要保证了它的同步,产生的实例就是唯一的,所以使用synchronized块,来保证线程之间的同步。 这样在完成了singleton初始化之后,便不会在进入synchronized中,系统的性能便不会受影响了。 程序看起来已经很完美了,事实确实是这样吗? 当然不是,再次被打脸了,分析一下,假设现在有两个线程A ,B 都执行到了 if() 出,线程A先执行了,获得了锁,此时完成了singleton的初始化操作,释放锁 ,B开始执行,因为B并不知道此时singleton已经完成了实例的创建,会再次创建一个实例。 好的,我们在改进一下 :

class LazySingleton{

    private volatile static  LazySingleton  singleton = null ;

    private LazySingleton(){}

    public static LazySingleton getInstance(){
        if(singleton == null ){
            synchronized (LazySingleton.class){
                if(singleton == null){
                    singleton = new LazySingleton() ;
                }
            }

        }
        return  singleton ;
    }
}

在synchronized块中,又进行了一次条件判断,这种方式称为双重检查锁定。 另外在变量的声明中添加了volatile关键字。关于volatile简单说两句,由于singleton使用了volatile修饰,一旦一个线程对改变做了修改,会马上由工作内存写回到主内存中,被其他线程所读取,工作内存可以理解为被线程独享,主内存可以理解为线程共享,被volatile修饰的成员变量可以确保多个线程都能够正确的处理,但是该代码只能在jdk1.5及以上的版本中才能正确执行。现在9都快要发布了, 相信现在还运行在1.5以下的程序应该不是很多了,应该影响不大, 这里只是提一下。 volatile关键字会禁止指令重排序优化,屏蔽到java虚拟机所做的一下代码优化,可能会导致运行效率降低, 这看起来也不是一个非常完美的实现方式。

饿汉式单例和懒汉式单例的比较

  • 饿汉式单例在类加载时就完成了初始化操作,因此无须考虑多线程的并发访问问题,正是由于它一开始就进行了实例化操作,从资源的利用和加载的时间上考虑可能比懒汉式要差一点
  • 懒汉式单例实现了延迟加载,但是在多线程并发访问的情况下对性能还是有一定的影响。

既然饿汉式和懒汉式都存在自己的问题,有没有一种方式能解决它们的问题呢,当然有,看下面代码:

通过使用静态内部类实现单例

public class StaticInnerClass {
    private StaticInnerClass(){}
    private static class InnerClass{
        private static final StaticInnerClass singleton = new StaticInnerClass() ;
    }

    public static StaticInnerClass getInstance(){
        return InnerClass.singleton ;
    }
}

调用getInstance()方法时,才进行实例的初始化操作,由于是static变量只会进行一次初始化操作,有JVM来保证线程的安全性。 即实现了延迟加载,有可以保证线程的安全。

枚举实现单例

//单例类
class Resource{
    public Resource(){
        System.out.println("resouce 构造方法");
    }
}

//枚举类生成Resource的单个实例
public enum EnumSingleton{
    INSTANCE ;

    private Resource resource ;
    private EnumSingleton(){
        System.out.println("EnumSingleton 构造方法执行");
        resource = new Resource() ;
    }

    public Resource getInstance(){
        return  resource ;
    }
}

//测试类及运行结果
public class Test {
    public static void main(String[] args) {
        Resource s1 = EnumSingleton.INSTANCE.getInstance() ;
        Resource s4 = EnumSingleton.INSTANCE.getInstance() ;
        Resource s3 = EnumSingleton.INSTANCE.getInstance() ;
        Resource s2 = EnumSingleton.INSTANCE.getInstance() ;

        if(s1==s2 && s2 ==s3 && s3==s4 && s1 ==s4){
            System.out.println("同一个引用");
        }
    }
}

//运行结果
EnumSingleton 构造方法执行
resouce 构造方法
同一个引用

在枚举类中构造方法私有化,外部无法访问,在访问枚举实例时会执行构造方法,实例化Resouce对象,枚举实例都是static final类型的,只能被实例化一次。这也是effective java中推荐的方式。

单例模式的优点:

  • 在内存中只有一个实例,减少了内存的开支,无须频繁的创建、销毁
  • 减少了系统的性能开销,避免了资源的多重占用

缺点:

  • 没有接口,扩展相对比较困难,违背了单一职责的原则

使用场景:

要求一个类有且仅有一个对象,只允许有一个公共的访问节点。 可以使用单例模式。


少年听雨歌楼上,红烛昏罗帐。  
壮年听雨客舟中,江阔云低,断雁叫西风。
感谢支持!
                                        ---起个名忒难

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 158,847评论 4 362
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 67,208评论 1 292
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 108,587评论 0 243
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 43,942评论 0 205
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 52,332评论 3 287
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,587评论 1 218
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,853评论 2 312
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,568评论 0 198
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,273评论 1 242
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,542评论 2 246
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 32,033评论 1 260
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,373评论 2 253
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 33,031评论 3 236
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,073评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,830评论 0 195
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,628评论 2 274
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,537评论 2 269

推荐阅读更多精彩内容