一、概述
1、定义
Ensure a class has only one instance,and provide a global point of access to it。(确保某一个类只有一个实例,而且自行实例化并向整个系统提供这个实例)。
2、单例模式的通用类图:
3、单例模式的几种实现
- 饿汉式(静态常量)
- 饿汉式(静态代码块)
- 懒汉式(线程不安全)
- 懒汉式(线程安全,同步方法)
- 双重检查
- 静态内部类
- 枚举
4、单例模式使用举例:
Hibernate的SessionFactory,充当数据存储源的代理,并负责创建Session对象。SessionFactory并不是轻量级的,一般情况下,一个项目通常只需要一个SessionFactory,因此这里使用的就是单例模式。
二、饿汉式
1、创建步骤
创建一个饿汉式(静态常量)单例模式的步骤如下:
- 构造器私有化(防止 new)
- 在类的内部创建对象
- 向外暴露一个静态的公共方法。getInstance()
2、代码实现(静态常量):
public class Singleton {
//本类内部创建对象实例
private static final Singleton instance=new Singleton();
//构造器私有化
private Singleton(){ }
//向外暴露的静态方法
public static Singleton getInstance(){
return instance;
}
}
3、代码实现(静态代码块)
public class Singleton {
private static Singleton instance;
//构造器私有化
private Singleton(){ }
static {//在静态代码块中创建单例对象
instance=new Singleton();
}
//向外暴露的静态方法
public static Singleton getInstance(){
return instance;
}
}
3、单例验证
我们可以通过以下两种方式来判断获取的实例是不是同一个实例:
Singleton singleton=Singleton.getInstance();
Singleton singleton1=Singleton.getInstance();
//方式1:返回true则是同一个
System.out.println(singleton==singleton1);
//方式2:hashCode相同则返回的是同一个实例
System.out.println("singleton.hashCode()-------------->"+singleton.hashCode());
System.out.println("singleton1.hashCode()-------------->"+singleton1.hashCode());
运行结果:
4、优缺点
优点:写法简单,在类装载的时候就完成了实例化,避免了线程同步问题。
缺点:在类加载的时候就完成实例化,没有达到Lazy Loading的效果。如果从始至终从未使用过这个实例,会造成内存的浪费。
三、懒汉式
1、懒汉式(线程不安全)
代码实现
public class Singleton {
//本类内部创建对象实例
private static Singleton instance;
//构造器私有化
private Singleton(){ }
//向外暴露的静态方法:当使用到该方法时,才去创建instance,达到了延迟加载的效果
public static Singleton getInstance(){
if(instance==null){
instance=new Singleton();
}
return instance;
}
}
优缺点分析:
实现了Lazy Loading的效果,但是只能在单线程下使用。
线程不安全的原因:如果在多线程下,一个线程进入了if(instance==null)
判断语句块,还未来得及往下执行,另一个线程也通过了这个判断语句,这时便会产生多个实例,即线程不安全。因此在多线程环境中不可使用这种方式。
2、懒汉式(线程安全,同步方法)
代码实现:
public class Singleton {
//本类内部创建对象实例
private static Singleton instance;
//构造器私有化
private Singleton(){ }
//向外暴露的静态方法
public static synchronized Singleton getInstance(){
if(instance==null){
instance=new Singleton();
}
return instance;
}
}
优缺点分析:
优点:使用synchronized
关键字进行同步,解决了线程不安全问题。
缺点:效率太低了,每个线程在通过getInstance()
获得类的实例的时候都要进行同步,而其实这个方法只执行一次实例化代码,之后想获得直接return就可以了。因此该方法不推荐使用。
四、双重检查
1、代码实现
使用了volatile
关键字,可以让我们的修改立即更新到主存,在此处可以理解为一个轻量级的synchronized
,后面会出一篇关于volatile
的理解。
public class Singleton {
//本类内部创建对象实例
private static volatile Singleton instance;
//构造器私有化
private Singleton(){ }
//向外暴露的静态方法,使用双重检查,解决线程安全问题,同时解决延迟加载问题,并保证了效率
public static Singleton getInstance(){
if(instance==null){
synchronized (Singleton.class){
if(instance==null){
instance=new Singleton();
}
}
}
return instance;
}
}
2、优缺点分析
保证了线程安全,实例化代码只执行一次,后面再次访问时,直接return,避免了同步方法的反复进行。
五、静态内部类
1、代码实现:
public class Singleton {
//构造器私有化
private Singleton(){ }
//静态内部类
private static class SingletonInstance{
private static final Singleton INSTANCE=new Singleton();
}
//向外暴露的静态方法
public static Singleton getInstance(){
return SingletonInstance.INSTANCE;
}
}
2、优缺点分析
- 采用了类装载机制保证了初始化实例时只有一个线程;并且类的静态属性只会在第一次加载类的时候初始化,在进行初始化时,别的线程是无法进入的。因此通过JVM帮我们保证了线程的安全性。
- 静态内部类方式在Singleton类被装载时并不会立即实例化,而是在调用
getInstance()
方法时才会装载静态内部类,从而完成Singleton的实例化。实现了延迟加载。
六、枚举
1、代码实现
public enum Singleton {
INSTANCE; //属性
}
2、优缺点分析
借助JDK1.5中添加的枚举来实现单例模式,不仅能避免多线程同步问题,而且还能防止反序列化重新构建新的对象。
七、单例模式的应用
1、单例模式的优点
- 内存中只有一个实例,减少了内存开支。
- 减少了系统的性能开销,当一个对象的产生需要比较多的内存资源时,如读取配置,产生其他依赖对象时则可以通过在应用启动时直接产生一个单例对象,然后用永久驻留内存的方式来解决。
- 单例模式可以避免对资源的多重占用。
- 单例模式可以在系统设置全局的访问点,优化和共享资源访问。例如设计一个单例类,负责所有数据表的映射处理。
2、单例模式的缺点
- 单例模式一般没有接口,扩展很困难;
- 单例模式对测试是不利的;
- 单例模式和单一职责原则有冲突;
3、单例模式的使用场景
- 要求生成唯一序列号的环境;
- 在整个项目中需要一个共享访问点或共享数据;
- 创建一个对象要消耗的资源过多,如访问IO和数据库等资源;
- 需要定义大量的静态常量和静态方法的环境,如工具类。
八、jdk源码中的单例模式应用举例
Runtime类中使用了饿汉式单例模式。具体看截图: