单例模式
单例模式(创建型模式):
涉及到的单一的类,该类只负责自己对象的创建,并且只有单个对象被创建,提供唯一的对象访问方式,可直接访问
注: 只能有一个实例作为全局的访问点, 构造函数私有单例类只能自己创建自己唯一的实例, 必须给所有其他对象提供这一实例; !!!使用synchronized/lock防止多线程同时创建多个实例
主要用于:控制资源,全局使用的类创建/销毁
优点: 只有一个实例,减少资源开销,避免对资源的多重占用(写文件操作)
缺点: 没有接口,不能被继承,只关心内部逻辑,不关心外部
使用场景–产品的唯一序列号; web计数器,使用单例将其缓存,不用每次在数据库中刷新; 创建对象消耗资源, 比如IO,数据库连接
懒汉模式/饿汉模式—在/不在类内方法构造实例
一般情况下,不建议使用第 1 种和第 2 种懒汉方式,建议使用第 3 种饿汉方式。只有在要明确实现 lazy loading 效果时,才会使用第 5 种登记方式。如果涉及到反序列化创建对象时,可以尝试使用第 6 种枚举方式。如果有其他特殊的需求,可以考虑使用第 4 种双检锁方式
给出下面的例子演示懒汉模式与饿汉模式:
public class SinglePatternDemo1 {public static void main(String[] args) {System.out.println(SinglePattern1.getInstance().hashCode());System.out.println(SinglePattern1.getInstance().hashCode());System.out.println(SinglePattern2.getInstance().hashCode());System.out.println(SinglePattern2.getInstance().hashCode());System.out.println(SinglePattern3.getInstance().hashCode());System.out.println(SinglePattern3.getInstance().hashCode());System.out.println(SinglePattern4.getInstance().hashCode());System.out.println(SinglePattern4.getInstance().hashCode());}}//懒汉式 不要求多线程 线程不安全
class SinglePattern1{private static SinglePattern1 instance=null;//构造方法私有处理避免,类可以实例化private SinglePattern1() {}public static SinglePattern1 getInstance() {if(instance==null)return instance=new SinglePattern1();return instance;}public void showMessage() {System.out.println("我是SinglePattern实例");}
}//懒汉式 线程安全 加锁保证效率低下
class SinglePattern2{private static SinglePattern2 instance=null;private SinglePattern2() {}public static SinglePattern2 getInstance() {synchronized(SinglePattern2.class){if(instance==null)return instance=new SinglePattern2(); return instance;}}
}//饿汉模式, 容易产生垃圾对象, 不需要加锁,执行效率会提高, 但是会浪费内存
class SinglePattern3{private static SinglePattern3 instance=new SinglePattern3();private SinglePattern3() {}public static SinglePattern3 getInstance() {return instance;}
}//双检锁/双重检验锁(DCL,double-checked Locking), 一定程度上具有安全性&&高性能
class SinglePattern4{private static SinglePattern4 instance=null;private SinglePattern4() {}public static SinglePattern4 getInstance() {if(instance==null) {synchronized(SinglePattern4.class) {if(instance==null)return instance=new SinglePattern4();}}return instance;}
}//登记式/静态内部类, 只适用于静态域延迟使用, 保证类加载的时候减缓实例化,
//并且使得实例不可在其他类加载的情况下, 再一次被加载
class SinglePattern5{private static class SinglePatternX{private static final SinglePattern5 instance=new SinglePattern5();}private SinglePattern5() {}public static SinglePattern5 getInstance() {return SinglePattern5.SinglePatternX.instance;}
}//枚举,即可以避免多线程, 还能自动支持序列化机制, 不能通过reflection attack调用私有构造法
enum SinglePattern6{instance;public void getMethod() {}
}
demo1线程不安全
demo2线程安全, 但是效率低下
demo3线程安全, 但是容易产生垃圾对象
demo4线程安全, 比demo2的效率增强了一点, 也可使用ReentrantLock处理
demo5线程安全, 完全利用类加载机制, 容易产生垃圾对象
demo6线程安全, 效率一般, 不能支持反射机制调用私有构造
双检锁/双重检验锁(DCL,double-checked Locking)可能会出现一个问题: 在new的过程中, 发生JVM指令重排, 就是当thread1构造对象的时候, thread2进入构造方法中, 发现object != null, 但是object在thread1下还没有构造好, 内部的数据还没有赋值, 此时返回object对象的引用将会出现this指针逃逸, 导致出现问题, 解决方案: 使用volatile修饰引用变量防止指令重排; 再次我也想说明, 可采用枚举做实例化, 可解决单例模式, 且线程安全