一 单例模式
1.单例的含义
单例(Singleton)是一种创建模式,它保证一个类只有一个实例,并提供一个全局访问点来访问该实例。实现单例模式的常见方法是使用静态方法和私有构造函数。
单例的含义可以归纳为以下几点:
-
确保类只有一个实例:使用单例模式可以防止多个对象的创建,保证了类的唯一性。
-
全局访问点:单例模式提供了一个全局的访问点,使得其他代码可以方便地获取到单例类的实例。
-
控制资源共享:单例模式可以用于控制共享资源,例如数据库连接、线程池等,确保资源的合理使用。
-
避免竞争条件:通过限制实例的数量,单例模式可以避免多个对象之间的竞争条件,从而提高程序的稳定性和性能。
总之,单例模式的含义是保证类只能创建一个实例,并提供全局访问的机制,以控制资源的共享和避免竞争条件。
2.单例的优缺点
单例模式的优点包括:
- 提供了对唯一实例的全局访问点。
- 节省了系统资源,因为只有一个实例存在。
- 避免了多个实例之间的数据不一致问题。
然而,单例模式也有一些缺点:
- 单例对象的状态一直存在于内存中,可能会导致内存泄漏问题。
- 对象的创建和销毁都由自身控制,可能造成资源的浪费。
因此,在使用单例模式时需要谨慎权衡其优缺点,确保在特定情况下使用单例是合适的。
3.单例的作用
在单例模式中,类的构造函数被设置为私有,以防止通过常规的实例化方法创建多个对象。然后,在类内部定义一个静态方法或属性,用于返回类的唯一实例。这个静态方法或属性会检查是否已经存在实例,如果不存在,则创建一个新的实例并返回;如果已经存在,则直接返回已有的实例。
单例模式通常用于需要共享资源或独占某些功能的场景,例如数据库连接、日志记录器等。它可以确保只有一个实例存在,避免了资源浪费和不必要的竞争条件。
4.单例的几种模式
实现单例模式的方式有多种,以下是两种常见的实现方法:
- 懒汉式(Lazy Initialization) 懒汉式是指在首次访问时才创建实例。具体实现如下:
public class Singleton { private static Singleton instance; private Singleton() { } public static synchronized Singleton getInstance() { if (instance == null) { instance = new Singleton(); } return instance; } }
在懒汉式中,通过静态变量
instance
延迟初始化。当第一次调用getInstance()
时,会检查instance
是否为空,如果为空则创建一个新的实例。由于涉及到多线程并发访问,需要在getInstance()
方法上添加synchronized
关键字来保证线程安全。 - 饿汉式(Eager Initialization) 饿汉式是指在类加载时就创建实例。具体实现如下:
public class Singleton { private static final Singleton instance = new Singleton(); private Singleton() { } public static Singleton getInstance() { return instance; } }
在饿汉式中,通过静态变量
instance
直接初始化,并在类加载时就创建了实例。由于在类加载时就创建了实例,因此不存在多线程并发访问的问题。 -
双检锁/双重校验锁(DCL,即 double-checked locking)
JDK 版本:JDK1.5 起
是否 Lazy 初始化:是
是否多线程安全:是
实现难度:较复杂
描述:这种方式采用双锁机制,安全且在多线程情况下能保持高性能。
getInstance() 的性能对应用程序很关键。public class Singleton { private volatile static Singleton singleton; private Singleton (){} public static Singleton getSingleton() { if (singleton == null) { synchronized (Singleton.class) { if (singleton == null) { singleton = new Singleton(); } } } return singleton; } }
-
登记式/静态内部类
是否 Lazy 初始化:是
是否多线程安全:是
实现难度:一般
描述:这种方式能达到双检锁方式一样的功效,但实现更简单。对静态域使用延迟初始化,应使用这种方式而不是双检锁方式。这种方式只适用于静态域的情况,双检锁方式可在实例域需要延迟初始化时使用。
这种方式同样利用了 classloader 机制来保证初始化 instance 时只有一个线程,它跟第 3 种方式不同的是:第 3 种方式只要 Singleton 类被装载了,那么 instance 就会被实例化(没有达到 lazy loading 效果),而这种方式是 Singleton 类被装载了,instance 不一定被初始化。因为 SingletonHolder 类没有被主动使用,只有通过显式调用 getInstance 方法时,才会显式装载 SingletonHolder 类,从而实例化 instance。想象一下,如果实例化 instance 很消耗资源,所以想让它延迟加载,另外一方面,又不希望在 Singleton 类加载时就实例化,因为不能确保 Singleton 类还可能在其他的地方被主动使用从而被加载,那么这个时候实例化 instance 显然是不合适的。这个时候,这种方式相比第 3 种方式就显得很合理。public class Singleton { private static class SingletonHolder { private static final Singleton INSTANCE = new Singleton(); } private Singleton (){} public static final Singleton getInstance() { return SingletonHolder.INSTANCE; } }
需要注意的是,单例模式虽然能够确保一个类只有一个实例,但也可能引入一些问题,如对资源的竞争和并发访问的性能影响。在使用单例模式时需要考虑到具体的需求和场景,合理选择适当的实现方式。
二 反射模式
1.反射的含义
大家都知道,要让Java程序能够运行,那么就得让Java类要被Java虚拟机加载。Java类如果不被Java虚拟机加载,是不能正常运行的。现在我们运行的所有的程序都是在编译期的时候就已经知道了你所需要的那个类的已经被加载了。
Java的反射机制是在编译并不确定是哪个类被加载了,而是在程序运行的时候才加载、探知、自审。使用在编译期并不知道的类。这样的特点就是反射,体现了Java的动态性。
每一个类都会在编译之后都会产生.class文件,当这个类在第一次使用的时候类加载器(ClassLoader)会加载.class到jvm中,
加载:由类加载器完成,找到对应的字节码,创建一个Class对象
链接:验证类中的字节码,为静态域分配空间
初始化:如果该类有超类,则对其初始化,执行静态初始化器和静态初始化块(编译器将检查类型向下转型是否合法,如果不合法将抛出异常。向下转换类型前,可以使用instanceof判断。)
2.反射作用
主要用于框架配置文件加载,反射常常配合工厂模式,代理模式共同作用于框架
假如我们有两个程序员,一个程序员在写程序的时候,需要使用第二个程序员所写的类,但第二个程序员并没完成他所写的类。那么第一个程序员的代码能否通过编译呢?这是不能通过编译的。利用Java反射的机制,就可以让第一个程序员在没有得到第二个程序员所写的类的时候,来完成自身代码的编译。
Java的反射机制它知道类的基本结构,这种对Java类结构探知的能力,我们称为Java类的“自审”。大家都用过Jcreator和eclipse。当我们构建出一个对象的时候,去调用该对象的方法和属性的时候。一按点,编译工具就会自动的把该对象能够使用的所有的方法和属性全部都列出来,供用户进行选择。这就是利用了Java反射的原理,是对我们创建对象的探知、自审。
3.反射的写法
在Java中,反射是由java.lang.reflect
包提供支持的。下面是一些常见的反射操作:
-
获取类的Class对象: 通过类的全限定名使用
Class.forName()
方法获取类的Class对象,或者直接通过类名的.class后缀获取。例如:Class<?> clazz = Class.forName("com.example.MyClass"); Class<MyClass> clazz = MyClass.class;
- 实例化对象: 使用反射可以通过Class对象的
newInstance()
方法来实例化对象。例如:MyClass obj = clazz.newInstance();
- 调用方法: 通过Class对象可以获取类的所有方法,然后可以使用Method对象来调用对应的方法。例如:
Method method = clazz.getMethod("methodName", parameterTypes); method.invoke(obj, args);
- 访问和修改字段: 通过Class对象可以获取类的所有字段,然后可以使用Field对象来访问和修改字段的值。例如:
Field field = clazz.getField("fieldName"); Object value = field.get(obj); field.set(obj, newValue);
- 创建实例和调用私有成员: 反射还提供了一些方法来创建实例、调用私有构造函数和访问私有成员。例如:
Constructor<?> constructor = clazz.getDeclaredConstructor(parameterTypes); constructor.setAccessible(true); // 允许访问私有构造函数 Object instance = constructor.newInstance(args); Method privateMethod = clazz.getDeclaredMethod("privateMethodName", parameterTypes); privateMethod.setAccessible(true); // 允许访问私有方法 privateMethod.invoke(obj, args);
通过反射,我们可以在运行时动态地操作类的成员,使得代码更加灵活。但同时也要注意,反射会带来一定的性能损耗,并且破坏了封装性,不当使用可能导致安全问题。因此,在使用反射时需要谨慎权衡其优缺点,并确保合理使用。