Java中单例设计模式,饿汉式和懒汉式

  Java中单例模式是一种常见的设计模式,单例模式的写法有好几种,这里主要介绍三种:懒汉式单例、饿汉式单例、登记式单例。
  单例模式有如下特色:
  一、单例类只能有一个实例。
  二、单例类必须本身建立本身的惟一实例。
  三、单例类必须给全部其余对象提供这一实例。
  单例模式确保某个类只有一个实例,并且自行实例化并向整个系统提供这个实例。在计算机系统中,线程池、缓存、日志对象、对话框、打印机、显卡的驱动程序对象常被设计成单例。这些应用都或多或少具备资源管理器的功能。每台计算机能够有若干个打印机,但只能有一个Printer Spooler,以免两个打印做业同时输出到打印机中。每台计算机能够有若干通讯端口,系统应当集中管理这些通讯端口,以免一个通讯端口同时被两个请求同时调用。总之,选择单例模式就是为了不不一致状态,避免政出多头。
java

1、懒汉式单例(面试常问)

 1 //懒汉式单例类.在第一次调用的时候实例化本身 
 2 public class Singleton {  3     private Singleton() {}  4     private static Singleton single=null;  5     //静态工厂方法 
 6     public static Singleton getInstance() {  7          if (single == null) {  8              single = new Singleton();  9  } 10         return single; 11  } 12 }

  Singleton经过将构造方法限定为private避免了类在外部被实例化,在同一个虚拟机范围内,Singleton的惟一实例只能经过getInstance()方法访问。面试

  (事实上,经过Java反射机制是可以实例化构造方法为private的类的,那基本上会使全部的Java单例实现失效。此问题在此处不作讨论,姑且掩耳盗铃地认为反射机制不存在。)设计模式

  可是以上懒汉式单例的实现没有考虑线程安全问题,它是线程不安全的,并发环境下极可能出现多个Singleton实例,要实现线程安全,有如下三种方式,都是对getInstance这个方法改造,保证了懒汉式单例的线程安全,若是你第一次接触单例模式,对线程安全不是很了解,能够先跳过下面这三小条,去看饿汉式单例,等看完后面再回头考虑线程安全的问题:缓存

一、在getInstance方法上加同步(性能较差)安全

1 public static synchronized Singleton getInstance() { 2          if (single == null) { 3              single = new Singleton(); 4  } 5         return single; 6 }

二、双重检查锁定(DCL 双检查锁机制)推荐多线程

 1 public static Singleton getInstance() {  2         if (singleton == null) {  3             synchronized (Singleton.class) {  4                if (singleton == null) {  5                   singleton = new Singleton();  6  }  7  }  8  }  9         return singleton; 10     }

三、静态内部类 推荐并发

1 public class Singleton { 2     private static class LazyHolder { 3        private static final Singleton INSTANCE = new Singleton(); 4  } 5     private Singleton (){} 6     public static final Singleton getInstance() { 7        return LazyHolder.INSTANCE; 8  } 9 }

  这种比上面一、2都好一些,既实现了线程安全,又避免了同步带来的性能影响。性能

 

 

2、饿汉式单例(用的多)

1 //饿汉式单例类.在类初始化时,已经自行实例化 
2 public class Singleton1 { 3     private Singleton1() {} 4     private static final Singleton1 single = new Singleton1(); 5     //静态工厂方法 
6     public static Singleton1 getInstance() { 7         return single; 8  } 9 }

  饿汉式在类建立的同时就已经建立好一个静态的对象供系统使用,之后再也不改变,因此天生是线程安全的测试

3、登记式单例(可忽略)

 1 package singleton;  2 
 3 import java.util.HashMap;  4 import java.util.Map;  5 
 6 /**
 7  * @author zsh  8  * @company wlgzs  9  * @create 2019-03-02 10:49 10  * @Describe 登记式单例 11  * 相似String里面的方法,将类名注册,下次从里面直接获取 12  */
13 public class Main3 { 14     private static Map<String,Main3> map = new HashMap<>(); 15     //初始数据
16     static { 17         Main3 main3 = new Main3(); 18  map.put(main3.getClass().getName(),main3); 19  } 20     protected Main3(){} 21     //静态工厂方法,返还此类的惟一实例
22     public static Main3 getInstance(String name){ 23         if (name == null){ 24             name = Main3.class.getName(); 25             System.out.println("Name == null,"+"--> name =" + name); 26  } 27         if (map.get(name) == null){ 28             try { 29  map.put(name, (Main3) Class.forName(name).newInstance()); 30             } catch (InstantiationException e) { 31  e.printStackTrace(); 32             } catch (IllegalAccessException e) { 33  e.printStackTrace(); 34             } catch (ClassNotFoundException e) { 35  e.printStackTrace(); 36  } 37  } 38         return map.get(name); 39  } 40 
41     public String print(){ 42         return "Hello"; 43  } 44 
45     public static void main(String[] args) { 46         Main3 instance = Main3.getInstance(null); 47  System.out.println(instance.print()); 48  } 49 }

运行结果:this

  登记式单例实际上维护了一组单例类的实例,将这些实例存放在一个Map(登记薄)中,对于已经登记过的实例,则从Map直接返回,对于没有登记的,则先登记,而后返回。

  这里我对登记式单例标记了可忽略,个人理解来讲,首先它用的比较少,另外其实内部实现仍是用的饿汉式单例,由于其中的static方法块,它的单例在类被装载的时候就被实例化了。

4、饿汉式和懒汉式区别

  从名字上来讲,饿汉和懒汉,

  饿汉就是类一旦加载,就把单例初始化完成,保证getInstance的时候,单例是已经存在的了,

  而懒汉比较懒,只有当调用getInstance的时候,才回去初始化这个单例。

  另外从如下两点再区分如下这两种方式:

  一、线程安全:

  饿汉式天生就是线程安全的,能够直接用于多线程而不会出现问题,

  懒汉式自己是非线程安全的,为了实现线程安全有几种写法,分别是上面的一、二、3,这三种实如今资源加载和性能方面有些区别。

  二、资源加载和性能:

  饿汉式在类建立的同时就实例化一个静态对象出来,无论以后会不会使用这个单例,都会占据必定的内存,可是相应的,在第一次调用时速度也会更快,由于其资源已经初始化完成,

  而懒汉式顾名思义,会延迟加载,在第一次使用该单例的时候才会实例化对象出来,第一次调用时要作初始化,若是要作的工做比较多,性能上会有些延迟,以后就和饿汉式同样了。

  至于一、二、3这三种实现又有些区别,

  第1种,在方法调用上加了同步,虽然线程安全了,可是每次都要同步,会影响性能,毕竟99%的状况下是不须要同步的,

  第2种,在getInstance中作了两次null检查,确保了只有第一次调用单例的时候才会作同步,这样也是线程安全的,同时避免了每次都同步的性能损耗

  第3种,利用了classloader的机制来保证初始化instance时只有一个线程,因此也是线程安全的,同时没有性能损耗,因此通常我倾向于使用这一种。

  什么是线程安全?

  若是你的代码所在的进程中有多个线程在同时运行,而这些线程可能会同时运行这段代码。若是每次运行结果和单线程运行的结果是同样的,并且其余的变量的值也和预期的是同样的,就是线程安全的。

  或者说:一个类或者程序所提供的接口对于线程来讲是原子操做,或者多个线程之间的切换不会致使该接口的执行结果存在二义性,也就是说咱们不用考虑同步的问题,那就是线程安全的。

5、应用

  如下是一个单例类使用的例子,以懒汉式为例,这里为了保证线程安全,使用了双重检查锁定的方式:

 
 1 package singleton;  2 
 3 /**
 4  * @author zsh  5  * @company wlgzs  6  * @create 2019-03-02 11:01  7  * @Describe 单例设计模式测试  8  */
 9 public class Main4 { 10     String name = null; 11     private Main4(){} 12 
13     private static volatile Main4 instance = null; 14 
15     public static Main4 getInstance(){ 16         if (instance == null){ 17             synchronized (Main1.class){ 18                 if (instance == null){ 19                     instance = new Main4(); 20  } 21  } 22  } 23         return instance; 24  } 25 
26     public String getName() { 27         return name; 28  } 29 
30     public void setName(String name) { 31         this.name = name; 32  } 33 
34     public void printInfo(){ 35         System.out.println("这个实例的名字为"+name); 36  } 37 }

  能够看到里面加了volatile关键字来声明单例对象,既然synchronized已经起到了多线程下原子性、有序性、可见性的做用,为何还要加volatile呢,缘由已经在下面评论中提到,见个人另外一篇博客:

 1 package singleton;  2 
 3 /**
 4  * @author zsh  5  * @company wlgzs  6  * @create 2019-03-02 11:05  7  * @Describe 单例设计模式测试主类  8  */
 9 public class Main5 { 10     public static void main(String[] args) { 11         Main4 main4 = Main4.getInstance(); 12         main4.setName("123"); 13         Main4 main41 = Main4.getInstance(); 14         main41.setName("456"); 15 
16  main4.printInfo(); 17  main41.printInfo(); 18 
19         if (main4 == main41){ 20             System.out.println("建立的是同一个实例"); 21         }else { 22             System.out.println("建立的不是同一个实例"); 23  } 24  } 25 }

 运行结果:

  结论:由结果能够得知单例模式为一个面向对象的应用程序提供了对象唯一的访问点,无论它实现何种功能,整个应用程序都会同享一个实例对象

  对于单例模式的几种实现方式,知道饿汉式和懒汉式的区别,线程安全,资源加载的时机,还有懒汉式为了实现线程安全的3种方式的细微差异。

相关文章
相关标签/搜索