设计模式相当于武功秘籍中的内功心法,学习设计模式,有助于我们在程序设计和开发中有更深程度的思考,在我们程序生涯中有着重要的意义。本文通过简单的描述来讲一下23种设计模式中的单例模式。
1、单例模式
保证一个类只有一个实例,并提供一个访问该实例的全局访问点
1.1、常见的场景
1、windows的任务管理器
2、Spring中的Bean
3、数据库连接池的设计
1.2、 优点:
单例模式只产生一个实例,减少了系统性能开销。如对象的产生需要较多的资源时,如读取配置,产生其他依赖对象时,可以在应用系统启动时产生一个单例对象,然后存在内存中。
1.3、 常见的五种单例模式实现方式
- 1、饿汉式
线程安全,调用效率高,但是不能延迟加载 - 2、饱汉式
线程安全,调用效率不高,可以延迟加载 - 3、双重检查锁式
由于JVM底层内部模型原因,偶尔会出问题,不建议使用 - 4、静态内部类
线程安全 调用效率高 实现了延时加载 - 5、枚举单例
线程安全,调用效率高,不能延迟加载
1.3.1、饿汉式
/**
* 饿汉式单例模式
* 线程安全,调用效率高,但是不能延迟加载
* @author tqq
* @date 2021-04-26-0:07
*/
public class SingletonDemo1 {
//类初始化时,立即加载这个对象(没有延迟加载的优势),加载类时,天然是线程安全的
private static SingletonDemo1 instance = new SingletonDemo1();
private SingletonDemo1(){ //私有化构造器
}
//方法没有同步,调用效率高
public static SingletonDemo1 getInstance(){
return instance;
}
}
注:
1、 static变量会在类装载时初始化,不会涉及到多个线程访问该对象;虚拟机只装载一次类,不会涉及到并发问题,可以省略synchronized关键字
2、 如果只装载本类,getInstance()不调用会造成资源浪费的问题
类初始化的时候就创建的好处:
1、如果初始化较长在程序启动的时候就完成初始化,就可以避免在程序运行的过程中初始化可能导致的性能问题。(如,在显影客户端接口请求时做初始化操作,可能导致相应时间过长,甚至超时)
2、如果实例占用资源多,我们希望有问题提前暴露,在程序启动时就触发报错,我们可以提前去修复,避免在程序运行一段时间后,突然因为因为初始化实例占用资源过多,影响系统的可用性。
1.3.2、饱汉式
/**
* 饱汉式单例模式
* 线程安全,调用效率不高,可以延迟加载
* @author tqq
* @date 2021-04-26-0:07
*/
public class SingletonDemo2 {
//类初始化时,不创建这个对象(延迟加载,真正用的时候再创建)
private static SingletonDemo2 instance;
private SingletonDemo2(){ //私有化构造器
}
//方法同步,调用效率低
public static synchronized SingletonDemo2 getInstance(){
if(instance==null){
instance = new SingletonDemo2();
}
return instance;
}
}
我们给getInstance()的方法加了一把锁,导致这个函数的并发度很低
如果这个单例类偶尔被调用,这种实现方式还可以接受;如果频繁地用到,那么频繁加锁、释放锁和并发度低等问题,会导致性能瓶颈。
1.3.3、双重检查锁式
package 单例设计模式;
/**
* 双重检查锁单例模式
* 由于JVM底层内部模型原因,偶尔会出问题,不建议使用
* @author tqq
* @date 2021-04-26-0:07
*/
public class SingletonDemo3 {
private static SingletonDemo3 instance = null;
public static SingletonDemo3 getInstance() {
if (instance == null) {
SingletonDemo3 sc;
synchronized (SingletonDemo3.class) {
sc = instance;
if (sc == null) { //这次判断是防止有并发问题
synchronized (SingletonDemo3.class) {
if(sc == null) {
sc = new SingletonDemo3();
}
}
instance = sc;
}
}
}
return instance;
}
private SingletonDemo3() {
}
}
public class SingletonDemo3 {
//volatile 保证可见性,可加可不加
private volatile static SingletonDemo3 instance = null;
private SigletonDemo3 (){};
public static SingletonDemo3 getInstance(){
if(instance == null){
synchronized (SingletonDemo3.class){
if(instance == null){
instance = new SingletonDemo3()
}
}
}
return instance;
}
}
1.3.4、静态内部类
/**
* 静态内部类实现方式(也是一种懒加载方式)
* 线程安全 调用效率高 实现了延时加载
* @author tqq
* @date 2021-04-26-9:59
*/
public class SingletonDemo4 {
//外部没有static属性,不会像饿汉式那样立即加载对象
//只有真正调用getInstance()才会加载静态内部类。加载类时是线程安全的。instance是static final类型,保证了内存中只有
//这样的一个实例存在,而且只能被赋值一次,从而保证了线程的安全性,
//兼备了并发高效调用和延迟加载的优势
private static class SingletonClassInstance{
private static final SingletonDemo4 instance = new SingletonDemo4();
}
public static SingletonDemo4 getInstance(){
return SingletonClassInstance.instance;
}
private SingletonDemo4(){
}
}
1.3.5、枚举
/**
* 枚举单例模式 (没有延时加载)
* 线程安全,调用效率高
* @author tqq
* @date 2021-04-26-10:11
*/
public enum SingletonDemo5 {
//这个枚举元素,本身就是个单例
INSTANCE;
public void singletonOperation(){
//功能处理
}
}
1.3.6 测试单例模式
/**
* 测试单例
* @author tqq
* @date 2021-04-26-10:15
*/
public class Client {
public static void main(String[] args) {
SingletonDemo1 s1 = SingletonDemo1.getInstance();
SingletonDemo1 s2 = SingletonDemo1.getInstance();
SingletonDemo2 s3 = SingletonDemo2.getInstance();
SingletonDemo2 s4 = SingletonDemo2.getInstance();
SingletonDemo4 s5 = SingletonDemo4.getInstance();
SingletonDemo4 s6 = SingletonDemo4.getInstance();
SingletonDemo5 s7 = SingletonDemo5.INSTANCE;
SingletonDemo5 s8 = SingletonDemo5.INSTANCE;
System.out.println(s1==s2); //true
System.out.println(s3==s4); //true
System.out.println(s5==s6); //true
System.out.println(s7==s8); //true
}
}
总结
1、当单例对象占用资源少,不需要延迟加载:
枚举式好于饿汉式
2、当单例对象占用资源大 需要延迟加载
静态内部类 好于饱汉式
程序源码
https://gitee.com/hellotqq/tqq-javase-study