创建者模式提供创建对象的机制,提升已有代码的灵活性和可复用性
创建者模式包括工厂模式、抽象工厂、生成器、原形、单例这 5 类
1、使用单例模式的原因
在开发中,有时候为了节省系统资源,我们需要确保系统中的某个类只有唯一一个实例,这个实例创建后,我们无法再创建一个同类型的其他对象,所有操作都是基于这个唯一的实例进行的。为了确保对象的唯一性,我们可以通过单例模式来进行实现。
2、单例模式概述
2.1 单例模式要点
- 一个类只能有一个实例对象,这个类称为单例类
- 这个类的实例必须是这个类自己创建的,而不是使用 new
- 这个类必须向整个系统提供这个实例,提供全局访问的方法
2.2 单例模式实现步骤
首先,我们定义一个类 SingleBean,包含了构造方法和初始化的方法,代码如下:
public class SingleBean {
public SingleBean(){}
public void init(){}
}
为了实现该类的唯一性,我们通过以下步骤对该类进行重构
-
首先为了防止每次使用 new 来实例化 SingleBean 都会产生一个新的对象,我们禁止类使用 new 来创建对象,将构造函数的修饰符改为 private
public class SingleBean { private SingleBean(){} public void init(){} }
-
外部已经无法使用 new 来创建对象了后,我们在需要在类的内部创建并保存一个唯一的实例,提供给外部进行访问。为了封装我们将成员变量修饰符设置为 private
public class SingleBean { private static SingleBean singleBean = null; private SingleBean(){} public void init(){} }
-
外界需要使用的时候,我们提供一个公共的静态方法对该类进行实例化
使用 public static 修饰,保证方法可以直接通过类名访问到,在方法中首先判断 singleBean 对象是否存在,如果不存在,则 new 一个新的 SingleBean 对象返回,否则直接返回已有的 singleBean 对象
public static SingleBean getInstance(){ if (singleBean == null) { singleBean = new SingleBean(); } return singleBean; }
3、饿汉式和懒汉式单例
3.1 饿汉式
静态常量:
在定义变量的时候就实例化单例类,在类加载的时候就已经创建了对象
类在加载时,静态变量 eagerSingle 会被初始化,创建一个唯一的实例
class EagerSingle{
private static final EagerSingle eagerSingle = new EagerSingle();
private EagerSingle(){}
public static EagerSingle getInstance(){
return eagerSingle;
}
}
静态代码块:
class EagerSingle{
private static EagerSingle eagerSingle;
private EagerSingle(){}
static {
eagerSingle = new EagerSingle();
}
public static EagerSingle getInstance(){
return eagerSingle;
}
}
3.2 懒汉式
同步方法:
懒汉式在第一次调用单例类提供的获取实例对象的方法时,进行实例化,类加载的时候不进行实例化。为了避免多个线程同时调用,我们可以使用关键字 synchronized
进行线程锁
class LazySingle {
private static LazySingle lazySingle = null;
private LazySingle(){}
public static synchronized LazySingle getInstance(){
if(lazySingle == null) {
lazySingle = new LazySingle();
}
return lazySingle;
}
}
同步代码块:
增加线程锁后,多线程访问每次都需要进行判断,我们还可以继续对代码进行优化
我们不用对整个方法加锁,可以对进行实例化的代码块加锁,进行如下改进:
class LazySingle {
private static LazySingle lazySingle = null;
private LazySingle(){}
public static LazySingle getInstance(){
if(lazySingle == null) {
synchronized(LazySingle.class){
lazySingle = new LazySingle();
}
}
return lazySingle;
}
}
双重检查:
如果对代码块进行加锁后,看似不会出现问题,但是如果多个线程同时调用 getInstance 方法,如果变量 lazySingle 对象为 null,都可以进入 if 条件内,线程依次执行代码块内容,导致多个对象的产生
所以我们还需要再进行一次判断,在加锁代码块内对变量再次进行判断。为了保证可见性,需要对变量增加 volatile
关键字,保证多个线程能够正确处理
class LazySingle {
private volatile static LazySingle lazySingle = null;
private LazySingle(){}
public static LazySingle getInstance(){
if(lazySingle == null) {
synchronized(LazySingle.class){
if (lazySingle == null) {
lazySingle = new LazySingle();
}
}
}
return lazySingle;
}
}
3.3 懒汉式与饿汉式单例比较
- 饿汉式在类加载时就进行了实例化,不用考虑多线程访问问题,确保实例唯一性。但饿汉式单例不管用不用得到都会进行实例化,始终占据内存
- 懒汉式在第一次使用时创建,延迟进行加载,为了处理多线程问题我们必须要进行双重锁定进行控制,性能会有一定影响
3.4 其它实现方式
静态内部类:
class SingleBean {
private SingleBean(){}
private static class SingleBeanInstance {
private final static SingleBean instance = new SingleBean();
}
public static SingleBean getInstance() {
return SingleBeanInstance.instance;
}
}
枚举:
public enum EnumSingleBean {
SINGLE_BEAN_INSTANCE;
private SingleBean singleBean = null;
private EnumSingleBean(){
singleBean = new SingleBean();
}
public SingleBean getInstance(){
return singleBean;
}
}
3.5 单例模式在 JDK 中的源码实现
java.lang.Runtime 就是一个经典的单例模式(饿汉式)
java.lang.Runtime 源码:
public class Runtime {
private static Runtime currentRuntime = new Runtime();
public static Runtime getRuntime() {
return currentRuntime;
}
private Runtime() {}
}