美文网首页
Java单例模式详解

Java单例模式详解

作者: 在挖坑的猿 | 来源:发表于2017-10-31 14:47 被阅读0次
  • 目录
    • 一.什么是单例?
    • 二.有几种?
    • 三.应用场景
    • 四.注意的地方

一.什么是单例?

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

通常我们可以让一个全局变量使得一个对象被访问,但它不能防止你实例化多个对象。一个最好的办法就是,让类自身负责保存它的唯一实例。这个类可以保证没有其他实例可以被创建,并且它可以提供一个访问该实例的方法 —— 大话设计模式--第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; 这样就可以保证每次都是从主内存中取,当然这样写或多或少的回影响性能,但是为了安全起见,这点性能牺牲还是值得。

相关文章

  • 单例模式安全之反射攻击

    单例模式安全之反射攻击 源码 单例模式这里就不谈了,什么是单例模式可参考七种Java单例模式详解,这里是关于单例模...

  • Java架构师课程

    Java架构班开学典礼 Spring中常用的设计模式概述及工厂模式详解 单例模式及原型模式单例模式及原型模式单例模...

  • 单例

    iOS单例模式iOS之单例模式初探iOS单例详解

  • 设计模式之单例模式详解

    设计模式之单例模式详解 单例模式写法大全,也许有你不知道的写法 导航 引言 什么是单例? 单例模式作用 单例模式的...

  • iOS 单例模式

    关于单例模式的详解,看完这几篇,就完全了然了。iOS 单例模式iOS中的单例模式iOS单例的写法

  • 单例模式

    单例模式及C++实现代码单例模式4种实现详解 c++11改进我们的模式之改进单例模式 单例模式(Singleton...

  • Java中单例模式你用的哪一种?

    一起讨论java中的单例模式。单例模式是java设计模式中算是最简单的设计模式了。 * java实现单例模式的写法...

  • JAVA设计模式 - 单例模式

    JAVA设计模式 - 单例模式 单例模式(Singleton Pattern)是 Java 中最简单的设计模式之一...

  • Java四种单例设计模式

    Java中的四种单例模式 单例模式是最容易理解的设计模式之一,介绍Java中单例模式的四种写法。 1.基本单例模式...

  • Java单例模式详解

    目录一.什么是单例?二.有几种?三.应用场景四.注意的地方 一.什么是单例? 单例模式 保证一个类在内存中只有一个...

网友评论

      本文标题:Java单例模式详解

      本文链接:https://www.haomeiwen.com/subject/pqwqpxtx.html