1、单例模式
只需要一个的对象,保证整个应用中某个实例有且只有一个,比如:
配置文件、工具类、线程池、缓存、日志对象等。
-
饿汉模式:
public class Singleton {
//1、构造方法私有化
private Singleton(){}
//2、创建类的唯一实例
private static Singleton instance = new Singleton();
//3、提供一个获取实例的方法
public static Singleton getInstance(){
return instance;
}
} -
懒汉模式:
public class Singleton {
//1、构造方法私有化
private Singleton(){}
//2、创建类的唯一实例,只声明不实例化
private static Singleton instance ;
//3、提供一个获取实例的方法,实例化
public static Singleton getInstance(){
if (instance == null){
instance = new Singleton();
}
return instance;
}
}
区别:饿汉模式加载类时比较慢,但运行时获取对象的速度比较快,线程安全;懒汉模式加载类时比较快,但运行时获取对象的速度比较慢,线程不安全。为了避免线程不安全的问题,采用双重校验锁
- 双重校验锁
public class Singleton {
//使用volatile防止JVM指令重排优化
private static volatile Singleton singleton = null;
private Singleton(){}
public static Singleton getSingleton(){
if (singleton == null){
synchronized (Singleton.class){
if (singleton ==null){
singleton = new Singleton();
}
}
}
return singleton;
}
}
因为 singleton = new Singleton() 这句话可以分为三步:
1. 为 singleton 分配内存空间;
2. 初始化 singleton;
3. 将 singleton 指向分配的内存空间。
但是由于JVM具有指令重排的特性,执行顺序有可能变成 1-3-2。 指令重排在单线程下不会出现问题,但是在多线程下会导致一个线程获得一个未初始化的实例。例如:线程T1执行了1和3,此时T2调用 getInstance() 后发现 singleton 不为空,因此返回 singleton, 但是此时的 singleton 还没有被初始化。
使用 volatile 会禁止JVM指令重排,从而保证在多线程下也能正常执行。
2、代理模式
对其他对象提供一种代理以控制对这个对象的访问。代理起到中介的作用,可去掉功能服务或增加额外的服务。
1、JDK动态代理的实现步骤
- 创建一个实现接口InvocationHandler的类,它必须实现invoke方法;
- 创建被代理的类以及接口;
- 调用Proxy的静态方法 ,创建一个代理类;
newProxyInstance(ClassLoader loader , Class[] interfaces , InvocationHandler h) - 通过代理调用方法
2、cglib动态代理
区别:JDK动态代理只能代理实现了接口的类,cglib可以针对类实现代理,使用继承的方式,不可以对final修饰的类进行代理。
3、观察者模式
定义对象间的一种一对多的依赖关系。当一个对象的状态发生改变时,所有依赖于它的对象都得到通知并被自动更新。
两种实现方式:推模型和拉模型
推模型:目标对象主动向观察者推送目标的详细信息,通常是目标对象的全部或部分数据,假定目标对象知道观察者需要的数控;
拉模型:目标对象在通知观察者的时候,只传递少量信息,如果观察者需要更具体的信息,由观察者主动到目标对象中获取,相当于是观察者从目标对象中拉数据(将目标对象自身通过update方法传递给观察者),目标对象不知道观察者具体需要什么数据,因此把自身传给观察者,由观察者来取值。