? 所谓类的单例设计模式,就是采取一定的方法保证在整个的软件系统中,对某个类 只能存在一个对象实例,并且该类只提供一个取得其对象实例的方法(静态方法)。
? 我们JDK中,java.lang.Runtime就是经典的单例模式(饿汉式)
? 优点:单例模式节省公共资源,单例模式方便控制
? 实现:
? 构造器私有化:如果要保证一个类不被多次实例化,就要阻止不能被new出来,私有就把所以构造器私有化。
? 类的内部创建对象:只对类进行一次实例化,以后都使用这一个实例化对象
? 以静态方法返回实例:因为外界不能new对象,所以我们需要提供一个公共的方法让外界拿到实例化对象。
饿汉式:先把对象创建好,要用时直接拿来用。
饿汉式虽然简单好用,但是却可能造成资源浪费,因为对象都是提前创建好的,我们却不一定会使用
package com.xin.singleton;
/*
单例模式:饿汉式(静态常量)
构造器私有化
类的内部创建对象
对外暴露一个静态公共方法
*/
public class Singleton {
//构造器私有,防止直接new
private Singleton() {}
//提供一个静态类型的Singleton01,先创建好实例
private static final Singleton instance = new Singleton();
//给一个公共方法,让外部获取实例
public static Singleton getInstance() {
return instance;
}
}
/*
写法简单,类装载的时候完成了初始化,避免了线程同步问题
没有达到 lazy loading(懒加载)效果,不使用就会造成内存资源浪费
*/
package com.xin.singleton;
/*
单例模式:饿汉式(静态代码块)
构造器私有化
类的内部创建对象
对外暴露一个静态公共方法
*/
public class Singleton {
//构造器私有,防止直接new
private Singleton() {}
//提供一个静态类型的Singleton01,先创建好实例
private static final Singleton instance;
//静态代码块中创建单例对象
static {
instance = new Singleton();
}
//给一个公共方法,让外部获取实例
public static Singleton getInstance() {
return instance;
}
}
/*
这种方式和静态常量方法类似,只是把实例化过程放到了静态代码块中,优缺点都一样
这种单例模式可用,但是也可能造成内存浪费
*/
因为饿汉式会造成内存浪费,所以就有了懒汉式
懒汉式就是不先创建对象实例,等我们需要用的时候再创建
package com.xin.singleton;
/*
单例模式:懒汉式(线程不安全)
*/
public class Singleton {
private static Singleton instance;
private Singleton() {}
//提供一个静态实例方法,调用该方法时才去创建实例对象
public static Singleton getInstance() {
if (instance == null) {
instance = new Singleton();
}
return instance;
}
}
/*
懒汉模式解决了饿汉模式可能引起的资源浪费问题,起到了懒加载效果,但是这种模式
在并发情况下会出现创建多个对象的情况,只适用于单线程模式,并发下并不安全
实际开发中,不使用这种情况
*/
package com.xin.singleton;
/*
单例模式:懒汉式(线程安全,同步方法)
*/
public class Singleton {
private static Singleton instance;
private Singleton() {}
//提供一个静态实例方法,调用该方法时才去创建实例对象
//加入同步处理代码,保证线程安全
public static synchronized Singleton getInstance() {
if (instance == null) {
instance = new Singleton();
}
return instance;
}
}
/*
这种情况保证了线程的安全,但是每一次调用方法时都要进行同步,大大的降低了效率,
而事实上这个方法执行一次就够了,后面调用return就可,开发中不推荐使用
*/
Double-Check概念是多线程开发中常使用到的,DCL是完美的解决了单例模式中性能和资源浪费的问题
package com.xin.singleton;
/*
单例模式:懒汉式(双检锁)
*/
public class Singleton {
private static Singleton instance;
private Singleton() {}
//提供一个静态实例方法,调用该方法时才去创建实例对象
//加入同步处理代码,保证线程安全
public static synchronized Singleton getInstance() {
//先验证是否创建对象
if (instance == null) {
//只有对象为创建才上锁
synchronized (Singleton.class) {
if (instance == null) {
instance = new Singleton();
}
}
}
return instance;
}
}
/*
但是DCL在并发情下也会存在一个问题,因为jvm指令是乱序的
对象实例化会有三个指令:
指令1:分配对象内存。
指令2:调用构造器,初始化对象属性。
指令3:构建对象引用指向内存。
而编译器会将指令进行优化,优化后顺序会变成:
1、执行指令1:分配对象内存,
2、执行指令3:构建对象引用指向内存。
3、执行指令2: 调用构造器,初始化对象属性。
而这样子就可能会发生一种情况:当线程1进行到第二步的时候,还没有初始化结果,这个时候cpu切换到了线程2,线程而就会走第一个判断,会发现实例不为空,进而直接返回,造成了线程2拿到了一个空的未初始化的对象。DCL 就失效了。
*/
解决指令重排问题,避免DCL失效:
? 使用 volatile 关键字
package com.xin.singleton;
/*
单例模式:懒汉式(双检锁)
*/
public class Singleton {
//使用volatile关键字,保证可见性,避免发生指令重排序
private static volatile Singleton instance;
private Singleton() {}
//提供一个静态实例方法,调用该方法时才去创建实例对象
//加入同步处理代码,保证线程安全
public static synchronized Singleton getInstance() {
//先验证是否创建对象
if (instance == null) {
//只有对象为创建才上锁
synchronized (Singleton.class) {
if (instance == null) {
instance = new Singleton();
}
}
}
return instance;
}
}
/*
双检测锁定的方式 是只有当对象未创建的时候才对请求加锁,对象创建以后都不会上锁,
这样,实例化代码只用执行一次,后面再次访问时,判断if (singleton == null),
直接return实例化对象,也避免的反复进行方法同步.线程安全;延迟加载;效率较高
DCL是完美的解决了单例模式中性能和资源浪费的问题
推荐使用
*/
package com.xin.singleton;
/*
单例模式:静态内部类
*/
public class Singleton {
private Singleton() {
}
// 提供一个静态方法,返回实例对象
public static Singleton getInstance() {
return SingletonHoler.singleton;
}
//定义静态内部类
private static class SingletonHoler {
//当内部类第一次访问时,创建对象实例
private static final Singleton singleton = new Singleton();
}
}
/*
这种方式采用了类装载的机制来保证初始化实例时只有一个线程
静态内部类方式在Singleton类被装载时并不会立即实例化,而是在需要实例化时,
调用getInstance方法,才会装载SingletonInstance类,从而完成Singleton的实例化。
类的静态属性只会在第一次加载类的时候初始化,所以在这里,JVM帮助我们保证了线程的安全性,在类进行 初始化时,别的线程是无法进入的。
避免了线程不安全,利用静态内部类特点实现延迟加载,效率高
推荐使用
*/
package com.xin.singleton;
/*
单例模式:枚举方式
*/
public enum Singleton {
INSTANCE; //属性
}
//枚举测试
public class Test {
public static void main(String[] args) {
System.out.println("使用枚举单例");
Singleton singleton1 = Singleton.INSTANCE;
Singleton singleton2 = Singleton.INSTANCE;
System.out.println(singleton1); //INSTANCE
System.out.println(singleton2); //INSTANCE
System.out.println(singleton1.hashCode()); //356573597
System.out.println(singleton2.hashCode()); //356573597
}
}
/*
枚举来实现单例模式。不仅能避免多线程同步问题,而且还能防止反序列化重新创建新的对象
推荐使用
*/
单例模式保证了 系统内存中该类只存在一个对象,节省了系统资源,对于一些需 要频繁创建销毁的对象,使用单例模式可以提高系统性能
当想实例化一个单例类的时候,必须要记住使用相应的获取对象的方法,而不是使用new
单例模式使用的场景:需要频繁的进行创建和销毁的对象、创建对象时耗时过多或 耗费资源过多(即:重量级对象),但又经常用到的对象、工具类对象、频繁访问数 据库或文件的对象(比如数据源、session工厂等)
原文:https://www.cnblogs.com/allure-xiaoxin/p/14643514.html