一 . 单例模式
① 什么是单例模式
· 单例模式:属于创建类型的一种常用的软件设计模式,通过单例模式的方法创建的类在当前进程中只有一个实例
简单来说单例模式就是指在内存中只会创建且仅创建一次对象的设计模式,当程序中其他地方需要使用到该对象的相同功能时,都会调用创建好的这一个,不会再额外创建实例,这样做的好处就是避免过多的创建相同作用的对象使得内存浪费。
② 单例模式分类
在单例模式中主要分为两类,分别是懒汉式和饿汉式
1. 懒汉式:在程序调用时才创建实例
2. 饿汉式:在程序加载时就创建好实例,等待被调用
③ 单例模式的实现
(1)懒汉式:
懒汉式是在程序调用时才会创建实例,在程序调用时首先会进行判断,如果已经存在该实例,则直接返回,若不存在该实例则创建并返回,懒汉式流程图如下:
代码实现:
为避免类被多次实例化,所以将类中的构造函数限定为private,这样就能保证其他程序无法通过new关键字来实例化,达到真正的单例。
package config;
public class Singleton {
//懒汉式
public static Singleton singleton;//初始定义空对象
private Singleton(){}
public static Singleton getInstance(){
if (singleton == null){
singleton = new Singleton();//创建对象
}
return singleton;//返回原有对象
}
通过以上代码就简单的实现了懒汉式的单例模式,如果当程序为多线程时,它们同时调用了Singleton的getInstence()方法,然后同时进入了if判断,这样的话由于线程不安全就会导致被实例化两个对象。
测试多线程调用单例模式
package Test;
import config.Singleton;
import org.junit.Test;
public class SingletonTest {
@Test
public void test(){
Thread thread = new Thread() {
public void run() {
Singleton singleton = Singleton.getInstance();
int hashCode = singleton.hashCode();
System.out.println(Thread.currentThread().getName() + ":" + hashCode);
}
};
thread.start();
Thread thread2 = new Thread() {
public void run() {
Singleton singleton = Singleton.getInstance();
int hashCode = singleton.hashCode();
System.out.println(Thread.currentThread().getName() + ":" + hashCode);
}
};
thread2.start();
}
}
运行结果为:
由结果我们知道两个线程所创建的对象的hashCode值不一样,那么也就代表如果多线程调用该单例模式则会出现线程安全问题。
(2)懒汉式优化:
说到解决线程安全问题,最先想到的肯定就是synchronized方法和synchronized代码块。
最简单的就是在getInstence方法上加上synchronized,这样当一个线程进入到该方法时,另外的线程就无法进入,可以确保单例。
//方法加synchronized锁的方式
public synchronized static Singleton getInstence() {
if(singleton== null)
singleton= new Singleton();
return singleton;
}
//synchronized代码块的方式
public static Singleton getInstence() {
synchronized(Singleton.class){
if(singleton== null)
singleton= new Singleton();
}
return singleton;
}
但是使用以上方法还是不够完美,那就是当已经有了实例之后每次调用getInstence还是会首先获取到锁,然后再进行判断,这样当高并发的情况下性能就会及其低下,所以如果需要安全并且性能好的话就只能使用以下方法。
最终优化方法
public Singleton getInstence() {
if(singleton == null) {
synchronized(Singleton.class) {
if(singleton== null) {
singleton = new Singleton();
}
}
}
return singleton;
}
该方法只是在synchronized代码块的方式外层又加了一层if判断,这样的话当已经有实例的情况下就会直接返回实例化并不会进入到if中,也就不会去获取锁。并且在没有实例的情况下如果两个线程都进入到了最外层的if判断,那么在线程A获取到锁并进入第二层if中并实例化对象之后,线程B就不会进入到第二层if,能够确保单例。
(3)饿汉式:
饿汉式是指在程序加载时就创建对象,当需要调用时则直接返回实例,不需要和懒汉式一样进行判断是否实例化,饿汉式流程图如下所示:
代码实现:
public class Singleton {
//饿汉式
private static final Singleton singleton = new Singleton(); //首先创建对象
private Singleton() {}
public Singleton getInstence() {
return singleton; //返回原有对象
}
}
(4)总结:
1.单例模式就是在内存中只会创建且仅创建一次对象的设计模式,因为只创建一次对象,所以构造方法私有化,通过getInstence方法获取对象。
2.单例模式分为懒汉式和饿汉式,懒汉式是在调用时创建对象,需要注意线程安全和性能优化,饿汉式是在程序加载时就创建对象,需要时直接调用。
3.在开发时如果对于内存的要求特别高,使用懒汉式,在需要时才创建,如果对内存要求不高使用饿汉式,饿汉式简单不易出错,而且没有并发安全和性能问题。
二 . 反射模式
① 什么是反射模式
大家都知道,要让Java程序能够运行,那么就得让Java类要被Java虚拟机加载。Java类如果不被Java虚拟机加载,是不能正常运行的。现在我们运行的所有的程序都是在编译期的时候就已经知道了你所需要的那个类的已经被加载了。
Java的反射机制是在编译并不确定是哪个类被加载了,而是在程序运行的时候才加载、探知、自审。使用在编译期并不知道的类。这样的特点就是反射,体现了Java的动态性。
② 反射模式的实现
实体类代码如下:
package domain;
import lombok.Data;
@Data
public class Student {
private Integer ID;
private String Name;
private Integer Age;
public String Address;
public void Show(){
System.out.println("无参的构造方法...");
}
private void Show1(String Name){
System.out.println("有参的构造方法...:"+Name);
}
}
测试类代码如下:
package Test;
import config.Singleton;
import domain.Student;
import lombok.SneakyThrows;
import lombok.val;
import org.junit.Test;
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.sql.Connection;
public class Test {
@Test
@SneakyThrows
public void Test2(){
//反射模式 全路径
Student student = new Student();
Class c1 = Class.forName("domain.Student");//获取类
Constructor<Student> constructor = c1.getConstructor();//获取构造函数
Student student1 = constructor.newInstance();
//公有的直接调用
student1.Address = "湖南长沙";
//私有的如何调用
Field name = c1.getDeclaredField("Name");
name.setAccessible(true);//开启私有的调用
name.set(student1,"尊嘟假嘟o.O");
Field age = c1.getDeclaredField("Age");
age.setAccessible(true);//开启私有的调用
age.set(student1,18);
System.out.println(student1.toString());
//------------------------拿方法
Class c2 = Class.forName("domain.Student");//获取类
Constructor<Student> constructor2 = c2.getConstructor();//获取构造函数
Student student2 = constructor2.newInstance();
//公有的直接调用
student2.Show();
//-----拿私有的构造方法
Method show1 = c2.getDeclaredMethod("Show1",String.class);
show1.setAccessible(true);
show1.invoke(student2,"假嘟尊嘟O.o");
}
}
运行结果如下:
③ 反射模式的作用
主要用于框架配置文件加载,反射常常配合工厂模式,代理模式共同作用于框架
④ 总结
反射模式(Reflection)是一种在计算机科学中常见的编程技术,它允许程序在运行时检查、访问和修改自身的结构、属性和行为。下面是对反射模式的总结:
-
定义:反射是指在运行时动态地获取和操作程序元素的能力,包括类、对象、方法、字段等。
-
功能:通过反射,程序可以在运行时获取类的信息,如类的名称、父类、接口、方法、字段等,并可以动态地创建对象、调用方法、访问和修改字段的值。
-
实现方式:在大多数编程语言中,反射都是通过提供特定的API或语法来实现的。例如,在Java中可以使用
java.lang.reflect
包提供的类和方法来实现反射。 -
应用场景:
- 动态加载类:通过反射可以根据类名动态加载并实例化对象,这在某些框架和库中非常常见,如Spring框架中的IOC容器。
- 调用未知方法:在某些情况下,我们可能需要根据运行时条件来确定调用哪个方法,反射提供了一种动态调用方法的方式。
- 修改私有字段和方法:通过反射,我们可以绕过访问修饰符的限制,直接访问和修改对象的私有字段和方法。
- 自动生成代码:反射可以帮助工具在运行时动态地生成代码,例如序列化库或ORM框架。
-
注意事项:
- 反射操作相比于常规编程方式更加复杂和耗时,因此在性能要求较高的场景下应慎重使用。
- 反射破坏了面向对象编程的封装性原则,因此应该谨慎使用,并遵循最小权限原则。
总之,反射模式提供了一种强大而灵活的编程技术,使程序能够在运行时动态地获取和修改自身的结构和行为。但是需要注意,在使用反射时需权衡其带来的复杂性和性能开销,并合理使用以保持代码的可维护性和安全性。