Java设计模式中的创建者模式/单例模式是啥?单例模式其中的饿汉式与懒汉式又是啥?又可以用在哪些地方
继续整理记录这段时间来的收获,详细代码可在我的Gitee仓库SpringBoot克隆下载学习使用!
4. 创建者模式
4.1 特点
- 使用者不需要知道对象的创建细节
4.2 单例模式
4.2.1使用场景
- 单例类:且仅能创建一个实例类
- 访问类:使用单例类
4.2.2 创建方式
4.2.2.1 饿汉式
- 类加载时就会创建单例对象
- 存在内存浪费问题
- 静态成员变量
public class SingletonDemo1 {
//私有构造函数
private SingletonDemo1(){}
//类中创建对象
private static SingletonDemo1 instance = new SingletonDemo1();
//提供访问方式,让外界获取
public static SingletonDemo1 getInstance(){
return instance;
}
}
测试代码,结果为true
// 获取对象
SingletonDemo1 singletonDemo1 =SingletonDemo1.getInstance();
SingletonDemo1 singletonDemo2 =SingletonDemo1.getInstance();
// 判断是否一样
System.out.println(singletonDemo1 == singletonDemo2);
}
- 静态代码块,测试类似,结果依旧为true
public class SingletonDemo2 {
//私有构造函数
private SingletonDemo2(){}
//类中创建对象
private static SingletonDemo2 instance ;
// 静态代码块赋值对象
static {
instance = new SingletonDemo2();
}
//提供访问方式,让外界获取
public static SingletonDemo2 getInstance(){
return instance;
}
}
4.2.2.2 懒汉式
- 使用对象时才创建对象
- 线程不安全方式,测试代码类似,单线程结果为true,多线程为false
public class SingletonDemo3 {
//私有构造函数
private SingletonDemo3(){}
//类中创建对象
private static SingletonDemo3 instance ;
public static SingletonDemo3 getInstance(){
// 若instance为null,则未创建,创建新对象,否则返回instance
if(instance == null)
instance = new SingletonDemo3();
return instance;
}
}
- 线程安全式,代码仅仅一点改动,测试代码类似,单线程多线程均为true,但执行效率低
//和之前一样
public static synchronized SingletonDemo3 getInstance(){
// 若instance为null,则未创建,创建新对象,否则返回instance
if(instance == null)
instance = new SingletonDemo3();
return instance;
}
- 双重检查锁式,解决效率低下问题,但在多线程下可能会空指针,原因是JVM在实例化对象中会进行优化和指令重排序
//和之前一样
public static SingletonDemo4 getInstance(){
// 若第一次判断instance不为null,不需要抢占锁,直接返回instance
if(instance == null)
{
synchronized (SingletonDemo4.class)
{
// 第二次判断
if(instance == null)
instance = new SingletonDemo4();
}
}
return instance;
}
改进则是加volatile
,比较推荐使用,如图
- 静态内部类,JVM加载外部类时不加载静态内部类,只有内部类属性/方法被调用时才会被加载并初始化静态属性结果为true
public class SingletonDemo5 {
//私有构造函数
private SingletonDemo5(){}
//类中创建对象
private static class Singleton{
private static final SingletonDemo5 INSTANCE = new SingletonDemo5();
}
//提供访问方式,让外界获取
public static SingletonDemo5 getInstance(){
return Singleton.INSTANCE;
}
}
4.2.2.3 枚举式(恶汉式)
4.2.2.3.1 特点
线程安全,只会装载一次,书写简单,唯一一种不会被破坏掉的方式
4.2.2.3.2 代码
public enum SingletonDemo6 {
INSTANCE;
}
测试类似,结果为true
4.2.3 存在问题
4.2.3.1 问题
会破坏单例模式唯一性
4.2.3.2 序列化及反序列化
- 源代码
public class SingletonDemo7 implements Serializable {
//私有构造函数
private SingletonDemo7(){}
//类中创建对象
private static SingletonDemo7 instance = new SingletonDemo7();
//提供访问方式,让外界获取
public static SingletonDemo7 getInstance(){
return instance;
}
}
- 测试代码,结果两者显示不一样,不是唯一对象,破坏单例模式
public static void readObject() throws Exception
{
ObjectInputStream objectInputStream = new ObjectInputStream(new FileInputStream("./a.txt"));
SingletonDemo7 singletonDemo1 = (SingletonDemo7) objectInputStream.readObject();
System.out.println(singletonDemo1);
objectInputStream.close();
}
public static void writeObject() throws Exception
{
ObjectOutputStream outputStream = new ObjectOutputStream(new FileOutputStream("./a.txt"));
outputStream.writeObject(SingletonDemo7.getInstance());
outputStream.close();
}
public static void main(String[] args) throws Exception {
// writeObject();
readObject();
readObject();
}
4.2.3.3 反射
- 源代码使用懒汉式静态成员变量代码
- 测试代码
public static void main(String[] args) throws Exception {
// 获取Singleton的字节码对象
Class clas = SingletonDemo1.class;
// 获取无参构造函数对象
Constructor declaredConstructors = clas.getDeclaredConstructor();
// 取消访问检查
declaredConstructors.setAccessible(true);
// 创建singleton对象
SingletonDemo1 o = (SingletonDemo1) declaredConstructors.newInstance();
SingletonDemo1 o1 = (SingletonDemo1) declaredConstructors.newInstance();
System.out.println(o == o1);
}
4.2.3.4 解决办法
4.2.3.4.1 序列化与反序列化
- 在Singleton类中添加readsolve()方法
- 如图
4.2.3.4.2 反射
- 加boolean 判断
- 如图
4.2.4 Runtime
4.2.4.1 特点
- 使用单例模式来进行对象创建
- 具体是饿汉式的静态成员变量,如图
4.2.4.2 简单使用
- 测试代码
public static void main(String[] args) throws Exception {
// 获取对象
Runtime runtime = Runtime.getRuntime();
// 执行控制台命令 ipconfig Process process = runtime.exec("ipconfig");
// 获取文件输入流
InputStream inputStream = process.getInputStream();
// 创建字节数组接收
byte[] bytes = new byte[1024*1024*100];
// 获取最终长度
int read = inputStream.read(bytes);
// 将字节数组转换为字符串
System.out.println(new String(bytes,0,read,"GBK"));
}
- 结果