一、创建型模式
1、单例模式
所谓单例,就是整个程序有且仅有一个实例。该类负责创建自己的对象,同时确保只有一个对象被创建。从而避免频繁的创建销毁对象,可以提高性能;避免对共享资源的多重占用;简化访问,为整个系统提供一个全局访问点。因为所属构造方法是私有,所以不支持继承。
众所周知,单例模式是创建型模式,都会新建一个实例。那么一个重要的问题就是反序列化。当实例被写入到文件到反序列化成实例时,我们需要重写readResolve方法,以让实例唯一。单例模式主要应用比如数据库连接池、应用配置。
(1)、懒汉模式
- public class Singleton {
- private static Singleton instance;
- //构造函数声明为private,是阻止外部实例化这个类。因为默认无参数构造方法是public的。
- private Singleton (){}
-
- public static Singleton getInstance() {
- if (instance == null) {
- instance = new Singleton();
- }
- return instance;
- }
- }
特点:延迟加载,需要的时候才使用
缺点:线程不安全。如果想线程安全的话可以在getInstance()方法加上Synchronized关键字,但是这样效率会很低
(2)、饿汉模式
- public class Singleton {
- private static Singleton instance = new Singleton();
- private Singleton (){}
-
- public static Singleton getInstance() {
- return instance;
- }
- }
特点:线程安全,方法没有加同步块,获取类的速度比较快,较为常用
缺点:在类初始化时,就需要加载这个对象,加载类的速度比较慢,不能延迟加载。如果加载初始化完不用容易产生垃圾,占用内存;
(3)、双重锁模式
- public class Singleton {
- private volatile static Singleton singleton;
- private Singleton (){}
-
- public static Singleton getSingleton() {
- if (singleton == null) {
- synchronized (Singleton.class) {
- if (singleton == null) {
- singleton = new Singleton();
- }
- }
- }
- return singleton;
- }
- }
volatile关键字:
①、保证此变量对所有的线程的可见性:这里的“可见性”,当一个线程修改了这个变量的值,volatile保证了新值能立即同步到主内存,以及每次使用前立即从主内存刷新。但普通变量做不到这点,普通变量的值在线程间传递均需要通过主内存来完成。
②、禁止指令重排序优化:有volatile修饰的变量,赋值后多执行了一个“load addl $0x0, (%esp)”操作,这个操作相当于一个内存屏障(指令重排序时不能把后面的指令重排序到内存屏障之前的位置),只有一个CPU访问内存时,并不需要内存屏障;(什么是指令重排序:是指CPU采用了允许将多条指令不按程序规定的顺序分开发送给各相应电路单元处理)。
特点:线程安全,延迟初始化。这种方式采用双锁机制,安全且在多线程情况下能保持高性能。双重检查模式,进行了两次的判断,第一次是为了避免不要的实例,第二次是为了进行同步,避免多线程问题。由于singleton=new Singleton()对象的创建在JVM中可能会进行重排序,在多线程访问下存在风险,使用volatile修饰signleton实例变量有效,解决该问题。
(4)、静态内部类模式
- public class Singleton {
- private Singleton(){
- }
- public static Singleton getInstance(){
- return Inner.instance;
- }
-
- private static class Inner {
- private static final Singleton instance = new Singleton();
- }
- }
特点:instance在创建过程中是线程安全的,所以说静态内部类形式的单例可保证线程安全,也能保证单例的唯一性,同时也延迟了单例的实例化。延迟加载,它的初始化操作跟外部类是分开的。在没有调用 getInstance() 方法之前,静态内部类不会进行初始化,在第一次调用该方法后就生成了唯一一个实例。JVM保证只有一个线程可以获得对象的初始化锁,其他线程无法进行初始化,保证对象的唯一性。静态内部类也有着一个致命的缺点,就是传参的问题,由于是静态内部类的形式去创建单例的,故外部无法传递参数进去。目前此方式是所有单例模式中最推荐的模式,但具体还是根据项目选择。
①、线程安全:JVM会保证一个类的方法在多线程环境中被正确地加锁、同步,如果多个线程同时去初始化一个类,那么只会有一个线程去执行这个类的方法(Java笔记中多线程的wait()、notify()和notifyAll()有涉及),其他线程都需要阻塞等待,直到活动线程执行方法完毕。如果在一个类的方法中有耗时很长的操作,就可能造成多个进程阻塞(需要注意的是,其他线程虽然会被阻塞,但其他线程唤醒之后不会再次进入类方法。同一个加载器下,一个类型只会初始化一次),在实际应用中,这种阻塞往往是很隐蔽的,所以是线程安全的。
②、延迟加载:JAVA虚拟机引用分为主动引用和被动引用,只有主动引用才会对类进行初始化。被动引用不会对类进行初始化,静态内部类就属于被动引用的行列,因此可以做到延迟加载。
(5)、枚举模式
- public enum Singleton {
- INSTANCE;
- public void fun(){}
- }
特点:枚举类隐藏了私有的构造器,并且天然支持多线程是线程安全的,在任何情况下都是单例。
缺点:可读性差