单例模式的引入
读取配置文件
考虑这样一个应用,读取配置文件的内容。很多项目里面,都有与应用先关的配置文件,这些配置文件很多都是由项目开发人员自定义的,在里面定义了一些应用所需要的参数。当然在实际的开发中,这个配置文件有很多种格式,如XML,properties ,现在要读取配置文件,该如何实现喃?
不用模式的解决方案
package com.study.design.pattern.singleton.example;
import lombok.Getter;
import lombok.Setter;
import lombok.ToString;
import java.io.IOException;
import java.io.InputStream;
import java.util.Properties;
/**
* 类的描述:读取应用配置文件
*
* @author: like
* @createDate: 2018年11月12日 20:24
* @version: v1.0
* @jdk version used: JDK1.8
* @see <相关类>
*/
@ToString
public class AppConfig {
/**
* 用来存放配置文件参数A的值
*/
private String parameterA;
/**
* 用来存放配置文件参数B的值
*/
private String parameterB;
public String getParameterA() {
return parameterA;
}
public void setParameterA(String parameterA) {
this.parameterA = parameterA;
}
public String getParameterB() {
return parameterB;
}
public void setParameterB(String parameterB) {
this.parameterB = parameterB;
}
public AppConfig(){
// 读取配置文件
readConfig();
}
private void readConfig() {
Properties p = new Properties();
InputStream in = null;
try{
in = AppConfig.class.getResourceAsStream("AppConfig.properties");
p.load(in);
this.parameterA = p.getProperty("paramA");
this.parameterB = p.getProperty("paramB");
}catch (IOException e){
e.printStackTrace();
}finally {
try {
in.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
测试类
package com.study.design.pattern.singleton.example;
import org.junit.Test;
import static org.junit.Assert.*;
/**
* 类的描述:
*
* @author: like
* @createDate: 2018年11月12日 20:38
* @version: v1.0
* @jdk version used: JDK1.8
* @see <相关类>
*/
public class AppConfigTest {
@Test
public void testReadProperties(){
AppConfig appConfig = new AppConfig();
String paramA = appConfig.getParameterA();
String paramB = appConfig.getParameterB();
System.out.println(appConfig.toString());
System.out.println("paramA = " + paramA +" paramB = " + paramB);
}
}
上面的实现很简单,但是有一个问题,就是在系统运行中,很多类都需要用到配置,如果在每一个类中都去 new
一个 Appconfig
,会严重的浪费内存资源,实际上,对于AppConfig这种类而言,只需要在运行的时候有一个实例就好了
总结:类在全局只需要一个实例,怎么实现?
解决方案
使用单例模式来解决问题
什么是单例模式?
单例模式的定义 : 保证一个类仅有一个实例,并提供一个全局的访问点
解决问题的思路
上面代码的问题在于构造方法是公开的,可以构建多个实例,所以构造方法不能公开。
单例模式结构图
imageSinglteton : 负责创建Singleton类的唯一实例,并提供一个getInstance的方法,让外部来访问这个类的唯一实例。
单例模式的分类
graph TD
A[单例模式] -->B(懒汉式)
A[单例模式] -->C(饿汉式)
1.懒汉式实现代码
package com.study.design.pattern.singleton;
/**
* 类的描述:测试懒汉式单例模式
* 线程安全,调用效率不高,但是可以延迟加载
*
* @author: like
* @createDate: 2018年11月05日 22:35
* @version: v1.0
* @jdk version used: JDK1.8
* @see <相关类>
*/
public class SingletonDemo2 {
/*
* 创建懒汉式的思路
*
* 1. 构造器私有化
* 2. 创建一个私有的static的类属性,懒汉式是创建属性的时候不创建对象
* 3. 提供一个public的方法用于获取对象
*/
/**
* 2. 创建一个私有的static的类属性 , 类初始化的,不加载对象,到使用的时候加载对象
*/
private static SingletonDemo2 singletonDemo2 ;
/**
* 1. 构造器私有化
*/
private SingletonDemo2(){
}
/**
* 3. 提供一个public的方法用于获取对象
* @return SingletonDemo2
*/
public static synchronized SingletonDemo2 getInstance(){
if(singletonDemo2 == null){
singletonDemo2 = new SingletonDemo2();
}
return singletonDemo2;
}
}
2.饿汉式实现代码
package com.study.design.pattern.singleton;
/**
* 类的描述:测试饿汉式单例模式
* 线程安全,调用效率高,但是不能延迟加载
*
* @author: like
* @createDate: 2018年11月05日 22:35
* @version: v1.0
* @jdk version used: JDK1.8
* @see <相关类>
*/
public class SingletonDemo1 {
/*
* 创建饿汉式的思路
*
* 1. 构造器私有化
* 2. 创建一个私有的static的类属性,饿汉式是创建属性的时候创建对象
* 3. 提供一个public的方法用于获取对象
*/
/**
* 2. 创建一个私有的static的类属性 , 类初始化的,立即加载对象
*/
private static SingletonDemo1 singletonDemo1 = new SingletonDemo1();
/**
* 1. 构造器私有化
*/
private SingletonDemo1(){
}
/**
* 3. 提供一个public的方法用于获取对象
* @return SingletonDemo1
*/
public static SingletonDemo1 getInstance(){
return singletonDemo1;
}
}
使用 <u>饿汉式</u> 单例模式重写实例
package com.study.design.pattern.singleton.example;
import lombok.ToString;
import java.io.IOException;
import java.io.InputStream;
import java.util.Properties;
/**
* 类的描述:使用饿汉式来重写 读取应用配置文件
*
* @author: like
* @createDate: 2018年11月12日 20:24
* @version: v1.0
* @jdk version used: JDK1.8
* @see <相关类>
*/
@ToString
public class AppConfigDemo1 {
private static AppConfigDemo1 instance = new AppConfigDemo1();
/**
* 用来存放配置文件参数A的值
*/
private String parameterA;
/**
* 用来存放配置文件参数B的值
*/
private String parameterB;
public String getParameterA() {
return parameterA;
}
public void setParameterA(String parameterA) {
this.parameterA = parameterA;
}
public String getParameterB() {
return parameterB;
}
public void setParameterB(String parameterB) {
this.parameterB = parameterB;
}
private AppConfigDemo1(){
// 读取配置文件
readConfig();
}
private void readConfig() {
Properties p = new Properties();
InputStream in = null;
try{
in = AppConfigDemo1.class.getResourceAsStream("AppConfig.properties");
p.load(in);
this.parameterA = p.getProperty("paramA");
this.parameterB = p.getProperty("paramB");
}catch (IOException e){
e.printStackTrace();
}finally {
try {
in.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
public static AppConfigDemo1 getInstance(){
return instance;
}
}
测试代码
package com.study.design.pattern.singleton.example;
import org.junit.Test;
import static org.junit.Assert.*;
/**
* 类的描述:测试饿汉式改造读取配置文件代码
*
* @author: like
* @createDate: 2018年11月12日 22:05
* @version: v1.0
* @jdk version used: JDK1.8
* @see <相关类>
*/
public class AppConfigDemo1Test {
@Test
public void test(){
AppConfigDemo1 demo1 = AppConfigDemo1.getInstance();
String paramA = demo1.getParameterA();
String paramB = demo1.getParameterB();
System.out.println("paramA = "+ paramA +" paramB = "+paramB);
System.out.println(demo1.toString());
}
}
单例模式总结
- 单例模式调用顺序示意图
懒汉式调用顺序
sequenceDiagram
client->>Singleton: 调用getInstance方法
Singleton ->> Singleton: 判断是否有实例,没有就创建,有就直接返回
Singleton-->>client: 返回singleton实例
饿汉式调用顺序
sequenceDiagram
client->>Singleton: 调用getInstance方法,直接返回,不用判断
Singleton-->>client: 返回singleton实例
扩展单例模式
双重检查加锁模式
package com.study.design.pattern.singleton;
/**
* 类的描述:双重检测锁模式
*
*
* @author: like
* @createDate: 2018年11月05日 22:35
* @version: v1.0
* @jdk version used: JDK1.8
* @see <相关类>
*/
public class SingletonDemo3 {
/*
* 创建懒汉式的思路
*
* 1. 构造器私有化
* 2. 创建一个私有的static的类属性,双重检测锁模式 是创建属性的时候不创建对象
* 3. 提供一个public的方法用于获取对象
*/
/**
* 2. 创建一个私有的static的类属性 , 类初始化的,不加载对象,到使用的时候加载对象
*/
private static SingletonDemo3 singletonDemo3 ;
/**
* 1. 构造器私有化
*/
private SingletonDemo3(){
}
/**
* 3. 提供一个public的方法用于获取对象
*
* 由于在方法上加 synchronized 每次查询和创建时都需要同步,效率低,
* 所有把 synchronized 放到里面,增加效率,只有第一次为 null 才需要同步
*
*
* 双重检查锁定是在输入同步块之前和之后检查延迟初始化对象的状态以确定是否初始化对象的做法。
* 如果没有针对float或int之外的任何可变实例的额外同步,它在与平台无关的方式下不能可靠地工作。
* 对于任何其他类型的原语或可变对象的延迟初始化,使用双重检查锁定会冒第二个线程使用未初始化或部分初始化的成员,
* 而第一个线程仍在创建它,并使程序崩溃。
* 有多种方法可以解决这个问题。
* 最简单的方法是根本不使用双重检查锁定,而是同步整个方法。对于JVM的早期版本,出于性能原因,通常建议不要同步整个方法。
* 但是同步性能在较新的JVM中得到了很大改善,因此现在这是一个首选的解决方案。
* 如果您希望完全避免使用synchronized,则可以使用内部静态类来保存引用。内部静态类保证延迟加载
*
* @return SingletonDemo3
*/
public static SingletonDemo3 getInstance(){
if(singletonDemo3 == null){
synchronized (SingletonDemo3.class){
if(singletonDemo3 == null){
singletonDemo3 = new SingletonDemo3();
}
}
}
return singletonDemo3;
}
}
内部类单例模式
内部类知识点扩展
- 什么是类级内部类?
- 简单的说,类级内部类指的是,有
static
修改的成员式内部类。如果没有static修改的成员式内部类被称为对象级内部类 - 类级内部类相当于其他外部类的static成分,它的对象与外部类对象间不存在依赖关系,因此可以直接创建。而对象级内部类的实例,是绑定在外部对象实例中的。
- 类级内部类中,可以定义静态方法。在静态方法中只能引用外部类中的静态成员变量或者方法
- 类级内部类相当于其他外部类的成员,只有在第一次被使用的时候才会被装载
代码示例:
package com.study.design.pattern.singleton;
/**
* 类的描述:静态内部类实现单例模式
* 外部类没有static属性,加载类的时候不会立即加载对象
* 只有外部真正调用 getInstance 方法的时候才会去加载内部类对象
* 兼容了并发高效调用和延迟加载的优势
*
*
* @author: like
* @createDate: 2018年11月05日 22:35
* @version: v1.0
* @jdk version used: JDK1.8
* @see <相关类>
*/
public class SingletonDemo4 {
/*
* 创建 静态内部类实现单例 的思路
*
* 1. 构造器私有化
* 2. 创建一个私有的static的类,静态内部类实现单例
* 3. 提供一个public的方法用于获取对象
*/
/**
* 1. 构造器私有化
*/
private SingletonDemo4(){
}
/**
* 3. 提供一个public的方法用于获取对象
*
* 由于在方法上加 synchronized 每次查询和创建时都需要同步,效率低,
* 所有把 synchronized 放到里面,增加效率,只有第一次为 null 才需要同步
*
* @return SingletonDemo4
*/
public static SingletonDemo4 getInstance(){
return SingletonClassInstance.singletonDemo4;
}
private static class SingletonClassInstance{
/**
* 2. 创建一个私有的static的类属性
*/
private static SingletonDemo4 singletonDemo4 = new SingletonDemo4();
}
}
枚举单例
单元素的枚举类型已经成为实现Singleton的最佳方法
枚举扩展
枚举资料:
- Java的枚举类型实际上是功能齐全的类,因此可以有自己的方法和属性
- Java枚举类型的基本思想是通过公有的静态final域为每个枚举常量导出实例的类
- 从某个角度讲,枚举式单例的泛型化,本质上是单元素的枚举
代码示例
package com.study.design.pattern.singleton;
/**
* 类的描述:枚举 实现单例模式
*
* 枚举本身就是单例
*
* 优点:
* 防止反射创建对象
* 缺点:
* 没有懒加载
*
* @author: like
* @createDate: 2018年11月05日 22:35
* @version: v1.0
* @jdk version used: JDK1.8
* @see <相关类>
*/
public enum SingletonDemo5 {
/**
* 这个枚举元素本身就是单例对象
*/
INSTANCE;
}
网友评论