历史最全单例模式总结

Posted 码农赵大爷

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了历史最全单例模式总结相关的知识,希望对你有一定的参考价值。


单例模式是最适合手撕代码的设计模式了,也可能是代码最少的设计模式了,但是少不一定意味着简单,想要用好,用对单例模式,还是需要费一些脑筋的。况且最近在准备面试刷《剑指Offer》时候,发现第一个就是写单例模式,而对单例模式没有一个全局的认识 ,经常看到懒汉式,饿汉

式这种词,今天决定全面梳理一下,加入我的面试题库中,当作标准答案,

背下来 下面对Java中常见的单例模式做一个最全的总结。 一个好的单例一

定满足:线程安全,延迟加载,序列化与反序列化安全

饿汉式:在第一次引用该类的时候就创建对象实例,而不管实际是否需

要创建,但是这样无法延迟加载,所以这种无法满足需求,由此就需要懒汉式:

public class Singleton { private static Singleton singleton = new Singleton(); private Singleton(){} public static Singleton getSingleton(){ return singleton; }}

懒汉式:分为单线程写法,考虑线程安全的写法,兼顾安全和效率的写法

单线程写法:由私有构造器和一个公有静态工厂方法构成,在工厂方法中

singleton进行null判断,如果是nullnew一个出来,最后返回singleton

对象,这种方法可以实现延迟加载,但是有一个致命弱点:线程不安全。如果

两个线程同时调用getSingleton()方法,就有很大可能导致重复创建对象,由此引出 考虑线程安全的写法:

public class Singleton{ private static Singleton singleton = null; private Singleton(){} public static Singleton getSingleton(){ if (singleton == null) { singleton = new Singleton(); } return singleton; }}

考虑线程安全的写法:这种写法考虑到了线程安全,将对singleton的

null判断以及 new的部分使用synchronized进行加锁。虽然这种写法

可以正确运行,但是效率低下,无法实际应用,原因是每次调用

getSingleton()方法,都需要在synchronized这里排队,而真正用到

new的情况是非常少的,所以引出下面的写法 :

 public class Singleton{ private static Singleton singleton = null; private Singleton(){} public static Singleton getSingleton(){ synchronized (Singleton.class){ if (singleton == null){ singleton = new Singleton(); } return singleton; } }}

兼顾安全和效率的写法:运用双重检查锁。在getSingleton()方法中,

进行两次null检查,看似多此一举,实际确极大提高了并发度。为什么

可以提高并发度呢,上文中提到过,在单例中 new的情况是非常少的

,绝大部分都是可以并行的读操作,因此在加锁前多进行一次null检查

可以杜绝绝大部分的加锁操作,达到提高并发度的目的。这种写法要

注意一点,由于用到了volatile这个关键字的禁止指令重排序语义,而

在JDK1.5及以后,可以保证禁止指令重排序。而在jdk1.5以前的版本,

即使将变量声明为volatile也无法完全避免重排序导致的问题。(这个关

键字有两个作用:1是作用于变量时的可见性,可见性指的是在一个线程

中对该变量的修改会马上由工作内存写回主内存,工作线程是线程独享

的,主内存是线程共享的。2是禁止指令重排序优化。由于现在的cpu都

多核心的,为了利用好多核性能,提升运行速度,编译器会对指令优

化,在实际执行的时候可能和我们编写的顺序不同,编译器能做的保证

是执行结果与源代码相同,而不保证实际指令的顺序与源代码相同。)


问题1:为什么singleton用private且static修饰?

答:这里用static是因为下面的方法是静态方法,静态方法不能调用非静态变量,所以设为static,
      用private是因为由于对象是static,必须要设置为private,因为若设置成public,就可以直接
调用对象而不用执行访问入口了
  问题2:为什么构造器是私有的?
答:因为不想被直接实例化,所以将构造器设为private的
问题3:getSingleton方法用static的原因?
答:若不用static,为实例方法,而实例方法必须通过对象调用,但是构造方法是private的,
无法直接new,所以设置为static,成为一个静态方法

public class Singleton{ private static volatile Singleton singleton = null; private Singleton(){} public static Singleton getSingleton(){ if (singleton == null){ synchronized (Singleton.class){ if (singleton == null){ singleton = new Singleton(); } } } return singleton; }}

推荐写法:静态内部类法:推荐理由:把singleton实例放到一个静态内

部类中,避免了静态实例在singleton类加载时候就创建对象,并且由于

静态内部类只会被加载一次,所以这种写法也是线程安全的

 public class Singleton{ private static class Holder{ private static Singleton singleton = new Singleton(); } private Singleton(){} public static Singleton getSingleton(){ return Holder.singleton; }}

上面的所有实现方式,都有两个共同的缺点: 1. 都需要额外的工作

(Serializable,transient,readResolve())来实现序列化,否则每次

反序列化一个序列化的对象实例时都会创建一个新的实例 2. 可能会有人

使用反射强行调用我们的私有构造器 effective java推荐写法:枚举写法,

理由:线程安全,可以防止反射强行调用构造器,提供了自动序列化机制

,防止反序列化时候创建新的对象。

public enum Singleton{ INSTANCE; private String name; public String getName(){ return name; } public void setName(String name){ this.name = name; }}


以上是关于历史最全单例模式总结的主要内容,如果未能解决你的问题,请参考以下文章

史上最全的二十三种设计模式总结

史上最全单例模式的写法和破坏单例方式

高并发下线程安全的单例模式(最全最经典,值得收藏)

单例模式(史上最全)

最全23种设计模式之单例模式(Singleton)

Sublime text3最全快捷键清单