单例模式
单例模式是java中最简单的设置模式之一,属于创建型设计模式,它提供了一种创建对象最佳方法. 这种模式涉及到一个单一的类,该类负责创建自己的对象,同时确保只有单个对象被创建,这个类提供了一种访问其唯一的对象方式,可以直接访问,不需要实例化该类的对象;
单例总结
1, 单例类只有一个实例;
2, 单例类必须自己创建自己的唯一实例;
3, 单例类必须给所有其他对象提供这一实例;
确保一个类仅有一个实例化,提供全局唯一访问点,主要解决了一个全局的类频繁的创建与销毁 ; 减少不必要的资源浪费;
关键代码
构造函数是私有的
使用单例模式优点
内存中一个类只有一个实例,减少了内存开销,尤其是频繁的创建和销毁实例;
缺点
没有接口,不能继承, 与单一职责原则冲突,只关心内部逻辑,不关心外面怎么实例化;
单例模式集中实现方式
饿汉式
public class Singleton {
private static Singleton instance = new Singleton();
private Singleton (){}
public static Singleton getInstance() {
return instance;
}
}
饿汉式在类加载时就实例化对了,使用时直接调用 getInstance() 方法。这个模式为线程安全的,在多线程并发模式下不会重复实例化对象。
优点: 效率高
缺点: 对象实例化过早,浪费内存资源
懒汉模式
public class Singleton {
private static Singleton singleton;
private Singleton() {
}
public static Singleton getInstance() {
if (singleton == null) {
singleton = new Singleton();
}
return singleton;
}
}
这种模式没有在加载时实例化对象,而是在调用getInstance() 方法时实例化对象,使用懒汉式是为了避免过早的实例化,减少内存资源浪费。
优点:第一次调用才初始化,避免内存浪费。
缺点: 只适合单线程,线程不安全
改进1 、懒汉式–引入synchronized
public class Singleton {
private static Singleton singleton;
private Singleton() {
}
// 添加了synchronized 关键字,保证线程安全
public static synchronized Singleton getInstance() {
if (singleton == null) {
singleton = new Singleton();
}
return singleton;
}
}
引入synchronized 关键字修饰getInstance() 方法,是为了解决线程不安全问题。利用多线程同步机制,然原先的线程不安全回归到线程安全。
优点:第一次调用才初始化,避免内存浪费。
缺点:引入synchronized 会因为线程阻塞、切换会带一些不必要的开销,从而降低了系统性能,效率会降低。
改进2、双检锁/双重校验锁
public class Singleton {
private static volatile Singleton singleton;
private Singleton() {
}
public static Singleton getSingleton() {
if (singleton == null) {
// 使用类锁
synchronized (Singleton.class) {
if (singleton == null) {
singleton = new Singleton();
}
}
}
return singleton;
}
}
对比改进1 ,可以看到synchronized不在修饰一个方法,而是缩小到修饰代码块,因为同步锁的范围越小,性能影响就越小,效率越高
这里可以注意到修饰变量instance的关键字增加了volatile。这里volatile主要作用是提供内存屏障,禁止指令重排序。
现有t1、t2两个线程同时访问getInstance(),假设t1、t2都执行到A处。由于有同步锁,只能有个1个线程获得锁,假如t1拥有该同步锁,t1执行到C处instace = new Singleton()。将会做如下3步骤:
1.分配内存空间
2.初始化
3.将instance指向分配内存空间
正常的执行顺序应为:1->2->3。执行第3步时,这时候的instance就不再是null了。但由于指令重排序的存在,执行顺序有可能变化为:1->3->2。当执行3的时候,instance就不再是null,但初始化工作有可能还没有做完。这时候如果t2获取锁执行的话,就会直接获取有可能还没有初始化完成的instance。这样使用instance会引起程序报错。当然这也是极端情况下,我尝试几次无法捕捉重现,但并不意味着问题不存在。volatile当然还是要加的。
A处if判断作用主要是防止过多是线程执行同步代码块;如果是单例模式的话,这里同步代码块只会被执行一次。B处if判断作用主要是防止多线程作用下重复实例化,保证线程安全。这也被称为:双重检查锁定。
双重检查锁定属于一种兼顾线程安全和性能的实现。
改进3、静态内部类
public class Singleton {
private Singleton(){}
private static class Holder {
public static Singleton instance = new Singleton();
}
public static Singleton getInstance() {
return Holder.instance; // 执行Holder的初始化工作
}
}
使用静态内部类也是懒汉模式的一种实现,当调用ggetInstance()才会触发加载静态内部类,从而初始化获取instance实例。利用静态内部类的加载机制来保证线程安全。
枚举
public enum Singleton {
INSTANCE;
public void whateverMethod() {
}
}
这种方式简洁,制动支持序列化机制,防止对此实例化,是实现单例模式最佳方法
用枚举方式实现单例模式,是目前比较推荐的。枚举方式的好处是:1、线程安全;2、防止反射出现多个实例;3、防止反序列化出现多个实例。