Java单例模式详解

  • 目录
    • 一.什么是单例?
    • 二.有几种?
    • 三.应用场景
    • 四.注意的地方

一.什么是单例?

单例模式 保证一个类在内存中只有一个实例(对象),并提供一个访问它的全局访问点。

通常我们可以让一个全局变量使得一个对象被访问,但它不能防止你实例化多个对象。一个最好的办法就是,让类自身负责保存它的唯一实例。这个类可以保证没有其他实例可以被创建,并且它可以提供一个访问该实例的方法 —— 大话设计模式--第21章

单例Boy.jpg

二.单例有几种?

具体区分为下面几种:

  • 饿汉式
  • 懒(饱)汉式
    • 线程不安全(单线程下操作的)
    • 线程安全
      • 方法加锁式
      • 双重判断加锁式DCL(Double-Check Locking)--- 方法加锁式加强版
  • 静态嵌套类式(推荐使用)
  • 枚举式(推荐使用)
  • 使用容器单例

不和你多bb ,上代码

b.jpg

1.饿汉式

public class Signleton{
    //对于一个final变量。  
    // 如果是基本数据类型的变量,则其数值一旦在初始化之后便不能更改;  
    // 如果是引用类型的变量,则在对其初始化之后便不能再让其指向另一个对象。
   private final static Signleton instance = new Signleton();
   
   private Signleton(){
       
   }
   
   public static  Signleton getInstance(){
        return instance;
   }
}

解释: 拿时间换空间,因为实例对象在类加载过程中就会被创建,在getInstance()方法中只是直接返回对象引用。因为这种创建实例对象方式比较‘急’,所以称为饿汉式。

优点:快很准,无需关心线程安全问题。

缺点
  1.无论对象会不会被使用,在类加载的时候就创建对象了,这样会降低内存的使用率。
   2.如果在一个大环境下使用了过多的饿汉单例,则会生产出过多的实例对象,无论你是否要使用他们

饿.jpg

2.懒(饱)汉式

(1)线程不安全(单线程下操作的)

public class Signleton{
  private static Signleton instance = null;
  
  private Signleton(){
  
  }
  
  public static Signleton getInstance(){
     if(instance==null){
        instance = new Signleton();
     }
     return instance;
  }
}

解释: Singleton的静态属性instance中,只有instance为null的时候才创建一个实例,构造函数私有,确保每次都只创建一个,避免重复创建。之所以被称为“懒汉”,因为它很懒,不急着生产实例,在需要的时候才会生产。

优点:延迟加载

缺点:只在单线程的情况下正常运行,在多线程的情况下,就会出问题。例如:当两个线程同时运行到判断instance是否为空的if语句,并且instance确实没有创建好时,那么两个线程都会创建一个实例。

(2)线程安全

方法加锁式
public class Signleton{
  private static Signleton instance = null;
  
  private Signleton(){
  
  }
  //给方法加锁 若有ABCD线程使用 A线程先进入 BCD线程都需要等待A线程执行完毕释放锁才能获得锁执行该方法
  //这样效率较低 
  public syschronized static Signleton getInstance(){
     if(instance==null){
        instance = new Signleton();
     }
     return instance;
  }
}

解释:给方法添加[synchronized],使之成为同步函数。两个线程同时想创建实例,由于在一个时刻只有一个线程能得到同步锁,当第一个线程加上锁以后,第二个线程只能等待。第一个线程发现实例没有创建,创建之。第一个线程释放同步锁,第二个线程才可以加上同步锁,执行下面的代码。由于第一个线程已经创建了实例,所以第二个线程不需要创建实例。保证在多线程的环境下也只有一个实例。

优点: 保证了线程安全。延时加载,用的时候才会生产对象。

缺点: 需要保证同步,付出效率的代价。加锁是很耗时的。。。

双重判断加锁式DCL
public class Signleton {
  private static Signleton instance = null;
  
  private Signleton(){
  
  }

  //假设有ABCD 个线程 使用这个方法
  public static Signleton getInstance(){
  //BCD都进入了这个方法
    if(instance==null){
      //而A线程已经给第二个的判断加锁了 
      syschronized(Signleton.class){
         //这时A挂起,对象instance还没创建 ,故BCD都进入了第一个判断里面,并排队等待A释放锁
         //A唤醒继续执行并创建了instance对象,执行完毕释放锁。
         //此时到B线程进入到第二个判断并加锁,但由于B进入第二个判断时instance 不为null了  故需要再判断多一次  不然会再创建一次实例
          if(instance==null){
             instance = new Signleton();
          }
       
      } 
    }
    return instance;
   }
}

解释:方法加锁式的优化。只有当instance为null时,需要获取同步锁,创建一次实例。当实例被创建,则无需试图加锁。。

优点: 保证了线程安全。延时加载,用的时候才会生产对象。进行双重判断,当已经创建过实例对象后就无需加锁,提高效率。

缺点: 编写复杂、难记忆。虽然是优化加锁式,但加锁始终会耗时。

3.静态嵌套类式(推荐使用)
public class Signleton{
   private Signleton{
   }
  
   //静态嵌套类  这里给个链接 区分静态嵌套类和内部类[静态嵌套类和内部类](http://blog.csdn.net/iispring/article/details/46490319)
   private static class  SignletonHolder{
      public static final Signleton instance = new Signleton();
  }
  
  public static Signleton getInstance(){
   return SignletonHolder.instance;
ins't
  }
  
}

解释: 定义一个私有的静态嵌套类,在第一次用这个嵌套类时,会创建一个实例。而类型为SingletonHolder的类,只有在Singleton.getInstance()中调用,由于私有的属性,他人无法使用SingleHolder,不调用Singleton.getInstance()就不会创建实例。

优点: 保证了线程安全。无需加锁,延迟加载,第一次调用Singleton.getInstance才创建实例。推荐使用。

缺点: 无。

4.枚举式(推荐使用)

为了方便理解枚举式 这边简单介绍一下枚举(在jdk1.5后)

//枚举Type
public enum Type{
   A,B,C;
  private String type;
  Type(type){
     this.type = type;
 }
 public String getType(){
    return type;
  }
}
//可认为等于下面的
public class Type{
  public static final Type A=new Type(A);
  public static final Type B=new Type(B);
  public static final Type C=new Type(C);
ins't
}

所以Type.A.getType()为A.
推荐去了解一下
Java学习整理系列之Java枚举类型的使用
Java学习整理系列之Java枚举类型的原理

好了 开始介绍枚举式了 看代码

public class Signleton{

public static Signleton getInstance(){
   return SignletonEnum.INSTANCE.getInstance();
}

public enum SignletonEnum{
   INSTANCE;
   
   private Signleton instance;
   
   //由于JVM只会初始化一次枚举实例,所以instance无需加static 
   private SignletonEnum(){
        instance = new Signleton();
   }
   
   public getInstance(){
       return instance;   
   }
}

}

解释: 定义内部的枚举,由于类加载时JVM只会初始化一次枚举实例,所以在构造函数中创建Signgleton对象并保证了这个对象实例唯一。
通过调用枚举INSTANCE方法getInstance (SignletonEnum.INSTANCE.getInstance())获取实例对象。

优点: 枚举提供了序列化机制--例如在我们要通过网络传输一个数据库连接的句柄,会提供很多帮助。

单元素的枚举类型已经成为实现Singleton的最佳方法。Effective Java

5.使用容器单例
public class Singleton { 
   //用Map保存该单例
    private static Map<String, Object> objMap = new HashMap<>(); 

    private Singleton() { 

    } 

    public static void putObject(String key, String instance){ 
        if(!objMap.containsKey(key)){ 
            objMap.put(key, instance); 
        } 
    } 

    public static Object getObject(String key){ 
        return objMap.get(key); 
    } 
}

解释: 在程序开始的时候将单例类型注入到一个容器之中 ,在使用的时候再根据 key 值获取对应的实例,这种方式可以使我们很方便的管理很多单例对象,也对用户隐藏了具体实现类,降低了耦合度。

缺点: 会造成内存泄漏。(所以我们一般在生命周期销毁的时候也要去销毁它) 。

三.应用场景

一般创建一个对象需要消耗过多的资源,如:访问I0和数据库等资源或者有很多个地方都用到了这个实例。

四.注意的地方

双重判断加锁式DCL :这种写法也并不是保证完全100%的可靠,由于 java 编译器允许执行无序,并且 jdk1.5之前的jvm ( java 内存模型)中的 Cache,寄存器到主内存的回写顺序规定,第二个和第三个执行是无法保证按顺序执行的,也就是说有可能1-2-3也有可能是1-3-2; 这时假如有 A 和 B 两条线程, A线程执行到3的步骤,但是未执行2,这时候 B 线程来了抢了权限,直接取走 instance 这时候就有可能报错。

简单总结就是说jdk1.5之前会造成两个问题:

1、线程间共享变量不可见性;

2、无序性(执行顺序无法保证);

当然这个bug已经修复了,SUN官方调整了JVM,具体了Volatile关键字,因此在jdk1.5之前只需要写成这样既可, private Volatitle static Singleton instance; 这样就可以保证每次都是从主内存中取,当然这样写或多或少的回影响性能,但是为了安全起见,这点性能牺牲还是值得。

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

推荐阅读更多精彩内容

  • 1 场景问题# 1.1 读取配置文件的内容## 考虑这样一个应用,读取配置文件的内容。 很多应用项目,都有与应用相...
    七寸知架构阅读 6,630评论 12 68
  • 前言 本文主要参考 那些年,我们一起写过的“单例模式”。 何为单例模式? 顾名思义,单例模式就是保证一个类仅有一个...
    tandeneck阅读 2,484评论 1 8
  • 单例模式(SingletonPattern)一般被认为是最简单、最易理解的设计模式,也因为它的简洁易懂,是项目中最...
    成热了阅读 4,222评论 4 34
  • 单例模式(Singleton Pattern)是众多设计模式中较为简单的一个,同时它也是面试时经常被提及的问题,如...
    廖少少阅读 553评论 0 1
  • 》昨晚喝了满满一湖的西湖水《 雨前闷热的空气 穹罩大地 如一空明不透的玻璃镜 要怎样 打破窒息般的结界 出门撒欢释...
    崖边草阅读 220评论 0 0