单例模式
一、单例设计模式
1、概念
- 即采取一定的方法保证在整个的软件系统中,对某个类只能存在一个对象实例,并且该类只提供一个取得其对象实例的方法。
2、三要素
- 私有的构造方法
- 私有的静态属性指向实例
- public static的 getInstance方法,返回静态属性绑定的实例
二、单例模式之懒汉式
1、概念
-
顾名思义,懒汉式就是在需要使用实例的时候才创建实例,即程序第一次访问单例模式实例时才进行创建。
-
示例:
public class Cat {
private String name;
private String age;
//保证一个类只有一个用于返回的对象
private static Cat instance;
//私有化构造器,保证外部无法创建实例。
private Cat(){
System.out.println("对象创建了");
}
//创建实例
public static Cat getInstance(){
//如果instance为空,则新 new 一个对象
if(instance==null){
instance = new Cat();
}
//如果不为空,则直接返回
return instance;
}
}
- 测试:
//测试
public static void main(String[] args){
Cat c = Cat.getInstance();
Cat c1 = Cat.getInstance();
System.out.println(c == c1);//true
}
2、线程安全问题
- 懒汉模式是线程不安全的。当有并发出现时,会new出多个实例。可以通过加锁使之变成线程安全的。
3、给 getInstance( ) 方法加锁
- 每次调用getInstance都会上锁,效率不高,适用于多线程环境
- 示例:
//给方法加锁。
public static synchronized Cat getInstance(){
//如果instance为空,则新 new 一个对象
if(instance==null){
instance = new Cat();
}
//如果不为空,则直接返回
return instance;
}
4、双重检验锁(适用于多线程,效率相对更高,推荐使用)
- 双重检验锁指在懒汉模式的基础上做进一步优化,给静态对象的定义加上volatile锁来保障初始化时对象的唯一性,在获取对象时通过synchronized (Cat.class)给单例类加锁来保障操作的唯一性。
- 示例:
public class Cat {
private String name;
private String age;
//volatile修饰成员变量 阻止指令重排序
private static volatile Cat instance;
//私有化构造器,保证外部无法创建实例。
private Cat() {
System.out.println("对象创建了");
}
//获取实例实例
public static Cat getInstance() {
if (instance == null) {
//实例为空再加锁
synchronized (Cat.class) {
if (instance == null) {
instance = new Cat();
}
}
}
//如果不为空,则直接返回
return instance;
}
}
运行过程:
- 检查变量是否被初始化(不去获得锁),如果已被初始化立即返回这个变量;
- 获取锁;
- 第二次检查变量是否已经被初始化:如果其他线程曾获取过锁,那么变量已被初始化,返回初始化的变量;
- 否则,初始化并返回变量。
问题:
- 双重检测较好的解决了懒汉模式开销大的问题,但首次加载的时候效率依然较低。
拓展: volatile关键字
三、单例模式之饿汉式
1、概念
- 饿汉式就是在类加载的时候就创建实例。饿汉式本身就是线程安全的
- 示例:
public class Cat {
private String name;
private String age;
//类加载就创建实例,加上final关键字,防止重复创建
private static final Cat instance = new Cat();
//私有化构造器,保证外部无法创建实例。
private Cat() {
System.out.println("对象创建了");
}
//获取实例的方法
public static Cat getInstance() {
return instance;
}
}
- 测试:
//测试
public static void main(String[] args){
Cat c = Cat.getInstance();
Cat c1 = Cat.getInstance();
System.out.println(c == c1);//true
}
四、单例模式之静态内部类
1、概念:
-
静态内部类通过在类中定义一个静态内部类,将对象实例的定义和初始化放在内部类中完成,我们在获取对象时要通过静态内部类调用其单例对象。之所以这样设计,是因为类的静态内部类在JVM中是唯一的,这很好地保障了单例对象的唯一性。
-
示例:
public class Cat {
private static class CatHolder {
private static final Cat INSTANCE = new Cat();
}
private Cat(){}
public static final Cat getInstance(){
return CatHolder.INSTANCE;
}
}
五、对比总结
1、饿汉式:
- 坏处:对象加载时间过长。
- 好处:线程安全
2、懒汉式:
- 好处:延迟对象的创建。
- 坏处:线程不安全--->到多线程内容时,再修改
3、注意:
- 如果一个对象使用频率不高,占用内存还特别大,明显就不合适用饿汉式了,这时就需要一种懒加载的思想,当程序需要这个实例的时候才去创建对象,就如同一个人懒的饿到不行了才去吃东西。