介绍单例模式和适配器模式的原理和实现。
单例和适配器模式
单例模式
此模式保证某个类在运行期间,只有一个实例对外提供服务,而这个类被称为单例类,单例模式典型代表是 ErrorContext,每个线程中有一个此对象的单例,用于记录该线程的执行环境的错误信息。
public class ErrorContext {
private static final String LINE_SEPARATOR = System.lineSeparator();
//使用 private 修饰的 ThreadLocal 来保证每个线程拥有一个 ErrorContext 对象
private static final ThreadLocal<ErrorContext> LOCAL = ThreadLocal.withInitial(ErrorContext::new);
public static ErrorContext instance() {
return LOCAL.get();
}
// 忽略其他
}
单例模式分为饿汉式和懒汉式
饿汉式
public class Singleton {
// 声明私有对象
private static Singleton instance = new Singleton();
// 获取实例(单例对象)
public static Singleton getInstance() {
return instance;
}
private Singleton() {
}
// 方法
public void sayHi() {
System.out.println("Hi,Java.");
}
}
class SingletonTest {
public static void main(String[] args) {
// 调用单例对象
Singleton singleton = Singleton.getInstance();
// 调用方法
singleton.sayHi();
}
}
优点是线程安全,因为单例对象在类加载的时候就已经被初始化了,缺点是如果类加载了单例对象(对象被创建了),但是一直没有使用,会造成资源浪费。
懒汉式
public class Singleton {
// 声明私有对象
private static Singleton instance;
// 获取实例(单例对象)
public static Singleton getInstance() {
if (instance == null) {
instance = new Singleton();
}
return instance;
}
private Singleton() {
}
// 方法
public void sayHi() {
System.out.println("Hi,Java.");
}
}
class SingletonTest {
public static void main(String[] args) {
Singleton singleton = Singleton.getInstance();
singleton.sayHi();
}
}
优点是不会造成资源的浪费,因为在调用的时候才会创建被实例化对象;它的缺点在多线程环境下是非线程是安全
DCL
我们需要既能保证线程安全,又能避免资源浪费的方式,采用双重检测锁
public class Singleton {
//使用volatile关键字是因为new Singleton()非原子操作
//需要防止CPU指令重排
private volatile static Singleton instance;
// 获取实例(单例对象)
public static Singleton getInstance() {
//双重检测,避免第一次判断后状态发生改变
// 第一次判断
if (instance == null) {
//用synchronized修饰代码块取代修饰方法
synchronized (Singleton.class) {
// 第二次判断
if (instance == null) {
instance = new Singleton();
}
}
}
return instance;
}
private Singleton() {
}
// 类方法
public void sayHi() {
System.out.println("Hi,Java.");
}
}
面试:为什么要 double-check?去掉任何一次的 check 行不行?”
- 去掉第一次check,线程会串行执行
- 去掉第二次话,现假设有两个线程同时调用
getInstance
方法,由于singleton
是空的 ,因此两个线程都可以通过第一重的 if 判断;然后由于锁机制的存在,会有一个线程先进入同步语句,并进入第二重 if判断 ,而另外的一个线程就会在外面等待。不过,当第一个线程执行完new Singleton()
语句后,就会退出synchronized
保护的区域,这时如果没有第二重 if (singleton == null) 判断的话,那么第二个线程也会创建一个实例,此时就破坏了单例
为什么要用volatile?
instance = new Singleton()
这一步操作实际上由三步,为了禁止指令重排造成结果错误,使用volatile
关键字修饰Singleton。
静态内部类
public class Singleton {
// 静态内部类
private static class SingletonInstance {
private static final Singleton instance = new Singleton();
}
// 获取实例(单例对象)
public static Singleton getInstance() {
return SingletonInstance.instance;
}
private Singleton() {
}
// 类方法
public void sayHi() {
System.out.println("Hi,Java.");
}
}
静态内部类只有在调用 getInstance() 方法时,才会装载内部类从而完成实例的初始化工作,因此不会造成资源浪费的问题。当初始化实例时只有一个线程执行,从而保证了多线程下的安全操作。
枚举
public class Singleton {
// 枚举类型是线程安全的,并且只会装载一次
private enum SingletonEnum {
INSTANCE;
// 声明单例对象
private final Singleton instance;
// 实例化
SingletonEnum() {
instance = new Singleton();
}
private Singleton getInstance() {
return instance;
}
}
// 获取实例(单例对象)
public static Singleton getInstance() {
return SingletonEnum.INSTANCE.getInstance();
}
private Singleton() {
}
// 类方法
public void sayHi() {
System.out.println("Hi,Java.");
}
}
class SingletonTest {
public static void main(String[] args) {
Singleton singleton = Singleton.getInstance();
singleton.sayHi();
}
}
不仅是线程安全的,而且只会装载一次,无论是序列化、反序列化、反射还是克隆都不会新创建对象。
适配器模式
适配器模式可分为对象适配器和类适配器两种,在对象适配器模式中,适配器与适配者之间是关联关系;在类适配器模式中,适配器与适配者之间是继承(或实现)关系。
角色
- Target(目标抽象类):目标抽象类定义客户所需接口,可以是一个抽象类或接口,也可以是具体类。
- Adapter(适配器类):适配器可以调用另一个接口,作为一个转换器,对Adaptee和Target进行适配,适配器类是适配器模式的核心。
- Adaptee(适配者类):适配者即被适配的角色,它定义了一个已经存在的接口,这个接口需要适配,适配者类一般是一个具体类,包含了客户希望使用的业务方法。
类适配器
//将要被适配的类
public class Adaptee {
public void adapteeRequest() {
System.out.println("被适配者的方法");
}
}
//定义一个目标接口
public interface Target {
void request();
}
//怎么才可以在目标接口中的 request() 调用 Adaptee 的 adapteeRequest() 方法呢?
public class Adapter extends Adaptee implements Target{
@Override
public void request() {
//...一些操作...
super.adapteeRequest();
}
}
通过一个适配器类,实现Target接口,同时继承了Adaptee类,然后在实现的request()方法中调用父类的 adapteeRequest()
Target adapterTarget = new Adapter();
adapterTarget.request();
//被适配者的方法
对象适配器
public class Adapter implements Target{
// 对象适配器(Adapter)将适配者(Adaptee)作为一个属性,而不是继承它
private Adaptee adaptee = new Adaptee();
@Override
public void request() {
//...
adaptee.adapteeRequest();
}
}
应用
AOP的适配器模式
//适配器
class ThrowsAdviceAdapter implements AdvisorAdapter, Serializable {
@Override
public boolean supportsAdvice(Advice advice) {
return (advice instanceof ThrowsAdvice);
}
//根据advice类型获取对应的拦截器
@Override
public MethodInterceptor getInterceptor(Advisor advisor) {
return new ThrowsAdviceInterceptor(advisor.getAdvice());
}
}
public class DefaultAdvisorAdapterRegistry implements AdvisorAdapterRegistry, Serializable {
private final List<AdvisorAdapter> adapters = new ArrayList(3);
public DefaultAdvisorAdapterRegistry() {
// 这里注册了三个适配器
this.registerAdvisorAdapter(new MethodBeforeAdviceAdapter());
this.registerAdvisorAdapter(new AfterReturningAdviceAdapter());
this.registerAdvisorAdapter(new ThrowsAdviceAdapter());
}
public MethodInterceptor[] getInterceptors(Advisor advisor) throws UnknownAdviceTypeException {
List<MethodInterceptor> interceptors = new ArrayList(3);
Advice advice = advisor.getAdvice();
if (advice instanceof MethodInterceptor) {
interceptors.add((MethodInterceptor)advice);
}
Iterator var4 = this.adapters.iterator();
while(var4.hasNext()) {
AdvisorAdapter adapter = (AdvisorAdapter)var4.next();
if (adapter.supportsAdvice(advice)) { // 这里调用适配器方法
interceptors.add(adapter.getInterceptor(advisor)); // 这里调用适配器方法
}
}
if (interceptors.isEmpty()) {
throw new UnknownAdviceTypeException(advisor.getAdvice());
} else {
return (MethodInterceptor[])interceptors.toArray(new MethodInterceptor[0]);
}
}
// ...省略...
}
- while 循环里,逐个取出注册的适配器,调用 supportsAdvice() 方法来判断 Advice 对应的类型,然后调用 getInterceptor() 创建对应类型的拦截器
- Advisor:Pointcut和Advice的配置器,它包括Pointcut和Advice,是将Advice注入程序中Pointcut位置的代码
- MethodInterceptor是Advice的子类,Spring使用MethodInterceptor来充当Advice