面试题答案
一键面试Java项目中常见的反模式
- 上帝类(God Class):
- 表现:一个类承担了过多的职责,拥有大量的方法和属性,几乎与系统中其他大部分类都有交互。例如,在一个电商系统中,有一个“全能业务类”,它既处理用户登录注册,又处理商品的添加、删除、修改,还负责订单的生成、支付和物流跟踪等所有核心业务逻辑。
- 识别方法:如果一个类的代码行数非常多,方法数量众多,并且这些方法涉及到多个不同的业务领域,或者该类与系统中大量其他类存在耦合关系,就可能是上帝类。
- 重复代码(Duplicated Code):
- 表现:在项目的不同地方出现了几乎相同或相似的代码片段。比如在一个Web应用中,在处理用户登录和用户注册的代码里,都有一段几乎一样的验证用户输入合法性的代码,只是变量名稍有不同。
- 识别方法:通过代码审查,人工观察代码片段的相似性。也可以借助一些工具,如PMD等静态代码分析工具,这些工具能够扫描代码库,发现重复度较高的代码块。
- 过度耦合(Over - Coupling):
- 表现:类与类之间的依赖关系过于紧密,一个类的变化很可能导致多个其他类需要修改。例如,在一个游戏开发项目中,游戏角色类直接依赖于游戏地图的具体实现类,当游戏地图的实现方式改变时,游戏角色类必须跟着修改。
- 识别方法:查看类的成员变量类型和方法参数类型,如果一个类的成员变量或方法参数中有大量来自其他具体类(而非接口或抽象类),说明这个类与这些具体类耦合度较高。同时,如果修改一个类,发现有许多其他类都需要跟着修改,也表明存在过度耦合。
单例模式使用过程中可能出现的反模式
- 多线程环境下单例创建的不合理情况:
- 懒汉式单例(非线程安全):
- 代码示例:
- 懒汉式单例(非线程安全):
public class LazySingleton {
private static LazySingleton instance;
private LazySingleton() {}
public static LazySingleton getInstance() {
if (instance == null) {
instance = new LazySingleton();
}
return instance;
}
}
- **问题分析**:在多线程环境下,当多个线程同时调用`getInstance`方法且`instance`为`null`时,可能会有多个线程都通过了`if (instance == null)`的判断,从而创建多个单例实例,破坏了单例模式的唯一性。
- **对系统造成的影响**:系统中可能出现多个本该唯一的对象实例,导致数据不一致。例如,在数据库连接单例模式中,如果出现多个数据库连接实例,可能会导致数据库资源的浪费,并且在对数据库进行操作时,可能因为不同实例的状态不同而出现数据错误。
- 双重检查锁定(早期实现存在问题):
- 代码示例(早期错误版本):
public class DoubleCheckedLockingSingleton {
private static DoubleCheckedLockingSingleton instance;
private DoubleCheckedLockingSingleton() {}
public static DoubleCheckedLockingSingleton getInstance() {
if (instance == null) {
synchronized (DoubleCheckedLockingSingleton.class) {
if (instance == null) {
instance = new DoubleCheckedLockingSingleton();
}
}
}
return instance;
}
}
- **问题分析**:在Java内存模型中,`instance = new DoubleCheckedLockingSingleton();`这行代码并非原子操作,它分为三步:分配内存空间、初始化对象、将`instance`指向分配的内存空间。在多线程环境下,由于指令重排序,可能会出现先执行了第三步,再执行第二步的情况。此时,如果另一个线程在`instance`指向内存空间但还未初始化对象时访问`instance`,就会使用到未初始化完全的对象,导致程序出错。
- **对系统造成的影响**:可能导致程序出现难以调试的错误,因为这种错误可能间歇性出现,依赖于线程的执行顺序和指令重排序的时机。程序可能在某些情况下正常运行,而在另一些情况下出现空指针异常或其他逻辑错误。
2. 正确的解决方式:
- 懒汉式单例(线程安全):
- 代码示例:
public class ThreadSafeLazySingleton {
private static ThreadSafeLazySingleton instance;
private ThreadSafeLazySingleton() {}
public static synchronized ThreadSafeLazySingleton getInstance() {
if (instance == null) {
instance = new ThreadSafeLazySingleton();
}
return instance;
}
}
- **原理**:通过在`getInstance`方法上加`synchronized`关键字,保证同一时间只有一个线程能进入该方法,从而避免了多线程下创建多个实例的问题。但这种方式会导致每次调用`getInstance`方法都需要进行同步操作,性能较低。
- 双重检查锁定(改进版本):
- 代码示例:
public class SafeDoubleCheckedLockingSingleton {
private volatile static SafeDoubleCheckedLockingSingleton instance;
private SafeDoubleCheckedLockingSingleton() {}
public static SafeDoubleCheckedLockingSingleton getInstance() {
if (instance == null) {
synchronized (SafeDoubleCheckedLockingSingleton.class) {
if (instance == null) {
instance = new SafeDoubleCheckedLockingSingleton();
}
}
}
return instance;
}
}
- **原理**:通过给`instance`加上`volatile`关键字,禁止了指令重排序,保证了`instance`初始化完成后才会被其他线程访问,同时结合双重检查锁定机制,既保证了线程安全,又提高了性能。
- 饿汉式单例:
- 代码示例:
public class EagerSingleton {
private static final EagerSingleton instance = new EagerSingleton();
private EagerSingleton() {}
public static EagerSingleton getInstance() {
return instance;
}
}
- **原理**:在类加载时就创建单例实例,由于类加载机制本身是线程安全的,所以这种方式天然线程安全。但它的缺点是如果单例对象创建成本较高,且可能一直不会被使用,会造成资源浪费。