单例设计模式
Posted figsprite
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了单例设计模式相关的知识,希望对你有一定的参考价值。
JVM将整个运行环境当做一个单例对象。
要点:
- 某个类只能有一个实例。
- 构造器私有化
- 它必须自行创建这个实例
- 含有一个该类的静态变量来保存这个唯一实例
- 它必须自行向整个系统提供这个实例
- 对外提供获取该实例对象的方式
- 直接暴露
- 用静态变量的get方法获取
几种常见形式:
-
饿汉式:直接创建对象,不存在线程安全问题
- 直接实例化饿汉式(简洁直观)
- 枚举式(最简洁)
- 静态代码块的饿汉式(适合复杂实例化)
-
懒汉式:延迟创建对象
- 线程不安全(适用于单线程)
- 线程安全(使用与多线程)
- 静态内部类形式(适用于多线程)
饿汉就是很着急,想吃东西,无论我当前要不要这些实例,它都着急着把它创建出来。
懒汉就是很懒,不到万不得已,不会去做,
我们先来总结一下刚刚提到的几点:
- 构造器虚拟化
- 自行创建,用静态变量存储
- 向外提供这个实例(因为是用静态变量存储,所以可以通过类名调用)
- 强调单例,可以使用final修饰
先来写饿汉式的单例,直接创建这个对象,无论是否需要它:
什么时候不需要这个静态对象呢?比如该类中含有静态方法,我可以通过类名直接调用,这时就没有这个对象啥事了,比如下图中的test()方法,但由于我们的INSTANCE是静态变量,在类加载时就已经创建,是不管test()用没用到的,都会创建出来。
在JDK1.5引入枚举之后,这种方式有更简洁的写法。
枚举类型,表示该类型对象是有限的几个,我们可以限定为一个,就成了单例。
非常简洁,但效果和我们之前写的第一种方法完全一样,获取方式也是可以通过类名.INSTANCE的方式获取。
第三种方式,看起来和第一种没啥太大区别,无非就是实例化使用静态代码块的方式实现,那我们什么时候回用到它?
比如说,我们有属性需要初始化:
这样写自然没问题,不过如果我们是通过配置文件的方式配置的,那该怎么办?
很显然,一二种方法很难做到这一点,第三种方法就可以在加载类时,将配置文件加载进来
为什么说饿汉模式不存在线程安全?因为JAVA中这些静态初始化的代码都会合成为<clinit>,<clinit>是线程安全方法,他们三个都是在类初始化的时候,直接创建的对象的。
接下来说说懒汉式:
要等到需要用这个单例时,我们才创建它,这样最初的时候instance应该是空的,为了避免外界在它还是空的时候通过类名.的方式获取,所以把instance设定为private。
在getInstance中,如果instance是空的,那么创建,如果不是,直接返回。
做一个测试:
Singleton4 singleton1 = Singleton4.getInstance(); Singleton4 singleton2 = Singleton4.getInstance(); System.out.println(singleton1==singleton2); System.out.println(singleton1); System.out.println(singleton2);
在单线程情况下,这个是没有问题的,如果是多线程呢?
也可以做一个测试,通过线程池的方式:
Callable<Singleton4> c = new Callable<Singleton4>() { @Override public Singleton4 call() throws Exception { return Singleton4.getInstance(); } }; ExecutorService es = Executors.newFixedThreadPool(2); Future<Singleton4> f1 = es.submit(c); Future<Singleton4> f2 = es.submit(c); Singleton4 s1 = f1.get(); Singleton4 s2 = f2.get(); System.out.println(s1==s2); System.out.println(s1); System.out.println(s2);
为了看出效果,我们在创建单例时加一个休眠代码:
public static Singleton4 getInstance() { if(instance==null) { try { Thread.sleep(100); } catch (InterruptedException e) { e.printStackTrace(); } instance = new Singleton4(); } return instance; }
结果很显然,在线程1发现instance没有初始化时,会进入if块中初始化instance,在线程1还没有来得及创建instance时,切换到线程2,它也发现instance没有初始化,也进入if块中,因此出现了两个instance,不符合单例的要求
我们可以用一个很暴力的方式,直接用synchronize包住这些代码,将Singlgton.class作为锁对象
package singleton; public class Singleton5 { private static Singleton5 instance; private Singleton5() { } public static Singleton5 getInstance() { synchronized (Singleton5.class) { if (instance == null) { try { Thread.sleep(100); } catch (InterruptedException e) { e.printStackTrace(); } instance = new Singleton5(); } return instance; } } }
做个测试:
当然这还不是最优版,最初来的时候,instance还没初始化,为了安全我们使用了synchronize,但是后面的获取行为就没有必要使用这个及其影响性能的synchronize了,所以用一个if可以大大提高性能
package singleton; public class Singleton6 { private static Singleton6 instance; private Singleton6() { } public static Singleton6 getInstance() { if(instance==null) { synchronized (Singleton6.class) { if (instance == null) { try { Thread.sleep(100); } catch (InterruptedException e) { e.printStackTrace(); } instance = new Singleton6(); } } } return instance; } }
最后一种是又简单,又有效的方式,在静态内部类加载初始化时,才创建INSTANCE实例对象,静态内部类不会自动随着外部类的加载和初始化而初始化,它是要单独去加载初始化的
package singleton; public class Singleton7 { ; private Singleton7() { } private static class Inner{ private static final Singleton7 INSTANCE = new Singleton7(); } public static Singleton7 getInstance() { return Inner.INSTANCE; } }
以上是关于单例设计模式的主要内容,如果未能解决你的问题,请参考以下文章