介绍
所谓类的单例模式 就是采取一定的方法保证在整个软件系统中对某个类只能存在一个对象实例,并且该类只提供一个取得其对象实例的方法(静态方法)
比如 Hibemate的SessionFactory 它充当数据存储源的代理 并负责创建Session对象 SessionFactory并不是轻量级的 一般情况下 一个 项目通常只需要一个SessionFactory就够 这样就需要用到单例模式了
单例模式的八种实现方式
饿汉式(静态常量)
public class HungryMan1 {
//1 构造器私有话 外部不能直接new对象调用
private HungryMan1(){}
//2 本类内部创建对象实例
private final static HungryMan1 instance = new HungryMan1();
//3 提供一个公有的静态方法 返回实例对象
public static HungryMan1 getInstance(){
return instance;
}
}
public class HungryManTest {
public static void main(String[] args) {
HungryMan1 instanbce = HungryMan1.getInstance();
HungryMan1 instabce2 = HungryMan1.getInstance();
System.out.println(instanbce == instabce2);
System.out.println(instanbce.hashCode());
System.out.println(instabce2.hashCode());
}
}
测试结果
优缺点说明:
(1) 优点: 这种写法比较简单, 就是在类装载的时候就完成了实例化 避免了线程同步问题
(2) 缺点: 在类装载的时候就完车实例化 没有达到lazy loading(懒加载)的效果 如果从始至终从未使用过这个实例 则会造成内存浪费
(3) 这种方式基于classloader(类装载器)机制避免了线程同步问题 不过,instance在类装载时就实例化 在单例模式中大多数都是调用getInstance方法 但是导致类装载的原因有很多种 因此不能确定有其他的方式(或者其他的静态方法) 导致类装载 这时候初始化instance就没有达到lazy loading的效果
(4) 结论: 推荐使用,但是可能会造成内存浪费
在jdk java.lang.Runtime#getRuntime()的源码中就是使用的饿汉式的单例模式
饿汉式(静态代码块)
public class HungryMan2 {
private HungryMan2(){}
//在静态代码块中 创建单例对象
static {
instance = new HungryMan2();
}
//2 本类内部创建对象实例
private final static HungryMan2 instance;
//3 提供一个公有的静态方法 返回实例对象
public static HungryMan2 getInstance(){
return instance;
}
}
测试同 "饿汉式静态常量"一致
优缺点说明
(1) 这种方式和上面的方法类似 只不过将类实例化的过程放在了静态代码块中,也是在类装载的时候就执行了静态代码块中的代码 初始化类的实例 优缺点和上面是一样的
(2) 结论:推荐使用,但是可能造成内存浪费
懒汉式(线程不安全)
public class LazyMan1 {
private static LazyMan1 instance;
private LazyMan1(){};
//提供一个静态的公有方法 当使用到该方法时 才会去创建 instance
public static LazyMan1 getInstance(){
if(instance==null){
instance = new LazyMan1();
}
//等价于 jdk8 Optional写法
// Optional.ofNullable(instance).orElseGet(()->new LazyMan1());
return instance;
}
}
测试同 "饿汉式静态常量"一致
优缺点说明
(1) 解决了lazy loading的问题 但是只能在单线程下使用 如果是多线程 可能会造成线程安全问题
(2) 如果再多线程下 一个线程进入了 if(instance==null)的判断语句块 还没来的及往下执行 另一个线程也通过了这个判断语句,此时就会产生多个实例 所以在多线程环境下不可以使用这种方式
(3) 结论: 在实际开发中 不要使用这种方式
懒汉式(线程安全 同步方法)
public class LazyMan2 {
private static LazyMan2 instance;
private LazyMan2(){};
//提供一个静态的公有方法 加入同步处理的代码 解决线程安全问题
public static synchronized LazyMan2 getInstance(){
if(instance==null){
instance = new LazyMan2();
}
return instance;
}
}
测试同 "饿汉式静态常量"一致
优缺点说明
(1) 解决了线程不安全问题
(2) 效率低 每个 线程在想获得类的实例的时候 执行getInstance()方法都要进行同步 而其实这个方法只需要执行一次实例化代码就可以了 后面的想获得该类实例 直接return就可以了 方法进行同步效率太低
(3) 结论: 在实际开发中 不推荐使用
懒汉式(线程安全 同步代码块)
public class LazyMan3 {
private static LazyMan3 instance;
private LazyMan3(){};
//提供一个静态的公有方法 加入同步处理的代码 解决线程安全问题
public static LazyMan3 getInstance(){
if(instance==null){
synchronized(LazyMan3.class){
instance = new LazyMan3();
}
}
return instance;
}
}
测试同 "饿汉式静态常量"一致
优缺点说明
(1) 这种方式 本意是想对 懒汉式(线程安全 同步方法)的改进,因为同步方法效率太低 改为同步产生实例化的代码块
(2) 但是这种同步不能起到线程同步的作用 跟 懒汉式(线程不安全) 遇到的情形一致 假如一个线程进入了 if(instance==null) 判断语句块 还没来得及往下执行 另一个线程也通过了这个判断语句 这时便会产生多个实例
(3) 结论: 在实际开发中 不要用这种方式
双重检查
public class LazyMan4 {
private static volatile LazyMan4 instance;
private LazyMan4(){};
//提供一个静态的公有方法 加入双重检查代码 解决线程安全问题 同时解决懒加载问题
public static synchronized LazyMan4 getInstance(){
if(instance==null){
synchronized(LazyMan4.class){
if(instance==null){
instance = new LazyMan4();
}
}
}
return instance;
}
}
测试同 "饿汉式静态常量"一致
优缺点说明
(1) double-check 概念是多线程开发中常使用到的 如代码中所示进行了两次 if(instance==null)检查 这样就可以保证线程安全了
(2) 这样 实例化代码只用执行一次 后面再次访问时 判断 if(instance==null),直接return实例化对象 也避免的反复进行方法同步
(3) 线程安全 延迟加载 效率较高
(4) 结论: 在实际开发中 推荐使用
静态内部类
public class LazyMan5 {
private LazyMan5(){};
//书写静态内部类 该类中有一个静态属性 LazyManInstance
private static class LasyManInstance{
private static final LazyMan5 INSTANCE = new LazyMan5();
}
//提供一个静态公有方法 直接返回LazyManInstance.INSTANCE
public static synchronized LazyMan5 getInstance(){
return LasyManInstance.INSTANCE;
}
}
测试同 "饿汉式静态常量"一致
优缺点说明
(1) 这种方式采用了类装载的机制来保证初始化实例时只有一个线程
(2) 静态内部类方式在LazyMan5类被装载时并不会立即实例化 而是在需要实例时 调用 getInstance方法 才会装载LazyManInstance类 从而完成LazyMan5的实例化
(3) 类的静态属性只会在第一次加载类的时候初始化 所以在这里 是jvm帮我们保证了线程的安全性 在类进行初始化时 别的线程是无法进入的
(4) 解决了线程安全问题 利用静态内部类特点实现延迟加载 效率高
(5)结论: 推荐使用
枚举
public class HungryManTest {
public static void main(String[] args) {
LazyMan6 in = LazyMan6.INSTANCE;
LazyMan6 in2 = LazyMan6.INSTANCE;
in.sayHello();
System.out.println(in==in2);
}
}
enum LazyMan6 {
INSTANCE;
public void sayHello(){
System.out.println("hello world!");
}
}
测试:
优缺点说明:
(1) 借助jdk1.5中添加的枚举来实现单例模式 不仅能避免多线程同步问题 而且还能防止反序列化重新创建新的对象
(2) 这种方式是Effective java作者Josh Bloch推荐的方式
(3)结论:推荐使用
单例模式的注意事项和细节说明
(1) 单例模式保证了系统内存中该类只存在一个对象 节省了系统资源 对于一些需要频繁创建销毁的对象 使用单例模式可以提高系统性能
(2) 当想实例化一个单例类的时候 必须要记住使用响应的获取对象的方法 而不是使用new
(3) 单例模式使用的场景: 需要频繁的进行创建和销毁的对象 创建对象时耗时过多或耗费资源过多(重量级对象) 但又经常用到的对象 工具类对象 频繁访问数据库文件的对象(比如数据源 session工厂等)