什么是单例设计模式
所谓 类的单例设计模式 ,就是采取一定的方法保证在整个的软件系统中,对某个类
只能存在一个对象实例
,并且 该类只提供一个取得该对象实例的静态方法。
单例设计模式的八种方式
饿汉式
在类加载时就创建对象,因此是线程安全的。
但是,有可能会造成资源浪费,因为 无论是否用到该对象,都会被创建。
创建步骤:
1、私有化构造器 : 防止外部类调用构造器进行对象创建;
2、类的内部直接new 一个对象;
3、提供一个 共有的 方法,返回 单例对象,供外部的类使用。
方式一:静态常量的方式初始化对象
java
public class SingleTon {
// 1、私有化构造函数
private SingleTon(){}
// 2、初始化静态变量
private static final SingleTon instance = new SingleTon();
// 3、提供静态方法返回对象
public static SingleTon getInstance(){
return instance;
}
}
方式二:静态代码块的方式初始化对象
java
public class SingleTon {
// 1、私有化构造器
private SingleTon(){}
// 2、声明一个静态的对象
private static final SingleTon singleTon;
// 3、静态代码块中初始化对象
static {
singleTon = new SingleTon();
}
// 4、提供公共的静态方法,返回对象
public static SingleTon getInstance(){
return singleTon;
}
}
验证代码
java
public class SingleTonTest {
public static void main(String[] args) {
// 获取单例对象
SingleTon singleTon1 = SingleTon.getInstance();
// 再获取一次单例对象
SingleTon singleTon2 = SingleTon.getInstance();
// 判断是否是同一个对象
System.out.println(singleTon1 == singleTon2);
// 打印对象的hashCode,看两个对象的hashcoe码是否相同
System.out.println("singleTon1.hashCode() = " + singleTon1.hashCode());
System.out.println("singleTon2.hashCode() = " + singleTon2.hashCode());
}
}
懒汉式
在用到对象的时候,才进行创建。
创建步骤:
1、私有化构造器 : 防止外部类调用构造器进行对象创建;
2、声明一个对象,但不进行实例化;
3、提供一个 共有的 方法,进行对象的实例化,并返回对象。(即 用到的时候才创建对象)
方式一 : 线程不安全
java
public class SingleTon {
// 1、私有化构造器
private SingleTon(){}
// 2、声明一个静态的对象
private static SingleTon singleTon;
// 3、提供公共的静态方法,返回对象
public static SingleTon getInstance(){
// 核心 : 判断对象是否为空,为空则创建对象,不为空则直接返回对象
// 缺点 : 存在线程安全问题
if (singleTon == null){
singleTon = new SingleTon();
}
return singleTon;
}
}
1、优点 : 在单线程下,起到了 懒加载 的效果;
2、缺点 : 在多线程下,当一个线程进入了
if (singleTon == null)
判断,还没创建对象时,另一个线程也进入了if (singleTon == null)
判断,此时便会产生多个实例。所以,多线程环境下,此方式不适用。3、结论 :
实际开发中,不推荐使用此方式。
方式二:线程安全-同步的方法
java
public class SingleTon {
// 1、私有化构造器
private SingleTon(){}
// 2、声明一个静态的对象
private static SingleTon singleTon;
// 3、提供公共的静态方法,返回对象,synchronized 保证线程安全
public static synchronized SingleTon getInstance(){
// 核心 : 判断对象是否为空,为空则创建对象,不为空则直接返回对象
if (singleTon == null){
singleTon = new SingleTon();
}
return singleTon;
}
}
1、优点 : 解决了线程安全的问题;
2、缺点 : 使用
synchronized
关键字对方法进行加锁,每次获取对象,都要进行同步,极大的降低了代码的执行效率。3、结论 :
在实际开发中,不推荐使用此中方式。
方式三:线程安全?-同步代码块
java
public class SingleTon {
// 1、私有化构造器
private SingleTon(){}
// 2、声明一个静态的对象
private static SingleTon singleTon;
// 3、提供公共的静态方法,返回对象
public static SingleTon getInstance(){
// 核心 : 判断对象是否为空,为空则创建对象,不为空则直接返回对象
if (singleTon == null){
// 同步代码块,线程安全?
// 但是上面的 if 判断条件没有线程安全,所以也 无法解决 线程安全问题
synchronized (SingleTon.class){
singleTon = new SingleTon();
}
}
return singleTon;
}
}
1、这种方式,由于
if (singleTon == null)
条件判断的原因,根本无法解决线程安全的问题!2、结论 :
在实际开发中,不能使用这种方式。
双重检查
下面的方法上的 synchronized 关键字是没有的,抄代码的时候抄错了
是对 【懒汉式】的一种优化。
1、volatile 关键字 :确保变量的可见性 : 变量修改后,刷新 主存中的数据,这样线程就能获取最新的值。
2、synchronized 关键字:提供同步控制,确保同一时刻,只能有一个线程执行 被 synchronized 修饰的方法或代码块。
java
public class SingleTon {
// 1、私有化构造器
private SingleTon(){}
// 2、声明一个静态的对象 : 使用 volatile 关键字修饰
private static volatile SingleTon singleTon;
// 3、提供公共的静态方法,返回对象
public static SingleTon getInstance(){
// 核心 : 判断对象是否为空,为空则创建对象,不为空则直接返回对象
if (singleTon == null){
// 核心 : 同步代码块
synchronized (SingleTon.class){
// 核心 : 二次判断对象是否为空
if (singleTon == null){
singleTon = new SingleTon();
}
}
}
return singleTon;
}
}
1、优点 :
Double-Check
的概念是多线程开发中经常使用到的。目的是 确保线程安全;2、优点 : 实例化对象的代码只用执行一次,后面再次访问时,判断条件
if(singleTon == null)
为 false,直接return 对象,避免了重复进行实例化的动作,确保了单实例的效果;
3、优点 :实现了懒加载的功能,效率较高。
4、结论 :
实际开发中,推荐使用。
静态内部类
静态内部类 : 外部类的一个成员,使用 static 修饰。
这种方式,是 将 单例类的 单例对象,作为 静态内部类的一个静态成员变量。
使用 类加载的机制 确保 线程安全和 单例。
java
public class SingleTon {
// 1、私有化构造器
private SingleTon(){}
// 2、声明一个静态内部类
private static class SingleTonHolder{
// 静态内部类中 有一个 单例的对象
private static SingleTon singleTon = new SingleTon();
}
// 3、提供公共的静态方法,返回对象
public static SingleTon getInstance(){
return SingleTonHolder.singleTon;
}
}
1、使用了 类加载的机制 来保证初始化实例时只有一个线程。
2、在
外部单例类 被加载时不回立即实例化 静态内部类;
3、在
调用 getInstance 方法时,会加载 静态内部类,完成 单例对象的实例化。
4、类的静态属性只会在第一次加载类的时候初始化,所以在这里,JVM 帮助我们保证了线程的安全性,在类进行初始化时,别的线程无法进入。
5、优点 :
线程安全,利用静态内部类的特点实现延迟加载。
6、结论 :
推荐使用。
枚举
枚举类 :
1、对象个数有限 且 确定;
2、类的构造方法 私有化 : private 修饰 - private 可以省略;
3、类的属性 : private final xxx xx;
一句话 : 枚举类 太适合搞单例了。
java
public enum SingleTonEnum {
// 单例对象
INSTANCE("张校长", 48);
// 对象属性
private final String name;
private final int age;
// 构造方法
SingleTonEnum(String name, int age)
{
this.name = name;
this.age = age;
}
}
1、优点 : 避免了多线程同步的问题,而且还能防止反序列化重新创建新的对象。
2、结论 :
推荐使用。
小结
使用场景
1、单例模式 保证了 系统内存中该类只存在一个对象,节省了系统资源,对于一些需要频繁创建销毁的对象,使用单例模式可以提高系统性能;
2、当想实例化一个单例类的时候,必须要记住,使用相应的获取对象的方法,而不是使用new;
3、单例模式的使用场景 : 需要频繁的创建和销毁的对象、创建对象时比较耗时或耗费资源过多(即:重量级对象),但是又经常用到的对象、工具类对象、频繁访问数据库或文件的对象(比如数据源、session工厂等)
使用建议
推荐使用
双重检查
、静态内部类
、枚举
的方式的 单例模式。不推荐使用
懒汉式