背景:听说单例模式是进入BAT的必经之路。
单例模式:
单例模式的就是确保一个类只有一个实例(即类的对象),并且只提供一个全局的访问点(外部通过这个访问点来访问该类的唯一实例)。
为何要设计单例模式&何种类可以设计成单例:
1、通过单例模式可以保证系统中只有一个实例而且该实例已于外界访问,从而方便对实例个数的控制并节约系统资源;
2、如果设计希望系统中的某个类的对象只能存在一个,单例模式就很吃香,如:在Windows中只能打开一个任务管理器、一个系统中可以存在多个打印任务但只能有一个正在工作的任务、项目中的配置文件信息等;
3、单例模式会阻止其他对象实例化自己的单例对象的副本,从而确保所有对象都访问唯一实例;
4、单例模式可以节省内存空间,减少无谓的GC消耗。
设计单例模式的几种方式:
Style-one是不考虑并发访问的情况下最简单的标准单例模式的构造方式(面试的时不要这么写Low);
1 public class Singleton {
2
3 //静态实例
4 private static Singleton singleton;
5 //构造函数私有化
6 private Singleton() {}
7 //公共的静态方法 返回一个单一实例
8 public static Singleton getInstance() {
9 if (singleton == null) {
10 singleton = new Singleton();
11 }
12 return singleton;
13 }
14 }
Style-one单例模式的设计要点:
1、静态实例,被static修饰的属性在每个类中都是唯一的;
2、构造器私有化,限制客户端任意创造实例对象,这也是保障其为单例的最重要的一步;
3、创建一个公共的获取实例的静态方法,该静态方法是为那些没有获取到实例的客户端提供的;
4、判断只有持有的静态实例为null时才调用构造方法创造一个实例,反之之间返回该实例。
style-two是考虑并发情况的单例模式设计,双重加锁;其实我们只需要在单例的实例还未创建的时候同步,在实例创建后,获取实例的方法是没必要进行同步控制的。
1 public class SynchronizedSingleton {
2
3 //静态实例
4 private static SynchronizedSingleton synchronizedSingleton;
5 //构造函数私有化
6 private SynchronizedSingleton() {}
7 //公共的静态方法 返回一个单一实例
8 public static SynchronizedSingleton getInstance() {
9 if (synchronizedSingleton == null) {
10 //只有当实例未被创建时才会加同步锁,比直接在SynchronizedSingleton方法前加synchronized同步好
11 synchronized(SynchronizedSingleton.class) {
12 if (synchronizedSingleton == null) {
13 synchronizedSingleton = new SynchronizedSingleton();
14 }
15 }
16 }
17 return synchronizedSingleton;
18 }
19
20 //不推荐如此设计,当多个线程方法时,会造成线程等待挂起等隐患
21 public synchronized static SynchronizedSingleton getInstance2() {
22 if (synchronizedSingleton == null) {
23 synchronizedSingleton = new SynchronizedSingleton();
24 }
25 return synchronizedSingleton;
26 }
27 }
Style-two单例模式的设计要点:
1、style-two中不建议使用直接在方法前用synchronized修饰保证同步,因为这样在一个线程访问该方法时,其他所有的线程都要处于挂起等待状态,徒增了无所谓的线程等待时间;
2、style-two中正确的双重加锁做法,只是在当前实例为null(即实例还未创建)时才进行同步,否则就直接返回实例对象,节省了无谓的线程等待时间;
3、style-two中的双重加锁其实还是存在一些缺陷的,依然可能会出现一些莫名的错误;因为在JVM中创建对象的过程大致可以分三步曲“1.分配内存 ---> 2.初始化构造器 ---> 3.将对象指向分配内存的地址“,在这个顺序下创建对象时style-two的双重加锁设计是OK的(因为JVM顺利完成了整个对象的构造才会将内存的地址交给对象);若是2和3的执行顺序发生改变就会有问题,因为这样就会导致先将内存地址赋给对象,再看双重加锁的设计就会先将分配好的内存地址给synchronizedSingleton,然后再进行初始化构造器,这时当后面的线程去请求getInstance方法时就会认为synchronizedSingleton对象已经实例化了,则会直接返回一个引用,但是,如果再初始化构造器之前的话,这个线程使用synchronizedSingleton时就会发生莫名的错误(语言级别无法避免的错误);
4、在JVM中初始化构造器和将对象指向分配内存的地址执行顺序对调是有可能出现的,因为JVM会针对字节码进行调优,其中的一项调优就是调整指令的执行顺序。
Style—three,为了避免上述莫名的错误,于是就有了一种比较标准的单例模式。
1 public class Singleton {
2
3 private Singleton(){}
4
5 public static Singleton getInstance(){
6 return SingletonInstance.instance;
7 }
8
9 private static class SingletonInstance{
10 static Singleton instance = new Singleton();
11 }
12 }
说他比较标准也不是吹的,因为一个类的静态属性只会在第一次加载该类的时候进行初始化,So我们没必要担心会有并发访问问题;另一方面当初始化进行到一半的时候,别的线程是无法使用的,因为JVM会帮我们强行同步这个过程;除此之外,由于静态变量只初始化一次,所以singleton依然是单例的;style-three它保证了:
1、singleton最多只有一个实例(在不考虑反射强行突破访问的情况下);
2、保证了并发访问情况下,不会发生由于并发而产生多个实例的问题;
3、保证了并发访问下,不会由于初始化未完成而造成使用了尚未正确初始化的实例的莫名错误;
Style—four:
俗称懒汉模式和饿汉模式的单例设计模式,与style-three的单例设计方式如出一辙,只不过会造成些许的内存浪费罢了,因为当访问了如下Singleton的任何其他的静态域就会造成实例的初始化,但我可能至始至终都没有用过这个实例。
饿汉模式:
1 public SingletonHunger(){
2 //饿汉模式
3 private static SingletonHunger sh=new SingletonHunger();
4
5 private SingletonHunger(){}
6
7 public static SingletonHunger getSingletonHunger(){
8
9 return sh;
10 }
11
12 }
懒汉模式:
1 class SingletonLazy{
2 //懒汉模式
3 private static SingletonLazy sl = null;
4
5 public SingletonLazy() {
6 if (sl == null)
7 sl = new SingletonLazy();
8 return sl;
9 }
10 }
By the way:
我们公司是4号放假的,1号我就请假溜回家了,预祝全国人民新年快乐,阖家欢乐。