文章目录
群u分享的小厂面经,我感觉还是很拷打的很基础的,没有很难的。
i++线程安全问题
1 ++操作符在单线程下面是安全的,在多线程下面不安全。
i++这个代码设计三个操作,首先需要到内存中读取遍历,将变量拿出来再++。最后再放到内存中去。操作不是原子性的,所以在单线程的下面是安全的,在多线程下面一定是不安全的。下面模拟了1000个线程同时操作i++。
a=a+b和a+=b的区别
这个其实拷打的是byte类型和int类型的数据范围问题,
b=b+1会发生数据类型的转化,会上线进行转型,如果还要变为byte需要进行强转。
a+=b 不会发生类型转换,还是保证的类型a;
synchronized
锁关键字,synchronized可以作用在代码块和方法上面。
这样就能保证线程安全问题了。
volatile关键字
在多线程环境下,volatile关键字可以保证共享数据的可见性,但是并不能保证对数据操作的原子性。
jdk1.8新增的特性
lamada表达式用来简化接口的实列化
stream流,可以用来统计数据,集合操作。
常量和变量
在Java中,常量和变量的区别可以从以下几个角度进行分析:
1. 定义与声明
常量
- 使用关键字:常量使用
final
关键字进行定义。 - 示例:
这里,final int MAX_VALUE = 100;
MAX_VALUE
被声明为常量,一旦赋值,不能再改变。
变量
- 普通变量声明:变量不使用
final
关键字,可以自由改变其值。 - 示例:
int value = 50; value = 60; // 可以改变变量的值
2. 值的可变性
常量
- 不可变性:常量在初始化后,其值不可改变。
- 示例:
final double PI = 3.14159; // PI = 3.14; // 错误,不能重新赋值
变量
- 可变性:变量的值可以在程序执行过程中多次改变。
- 示例:
int count = 10; count = 20; // 合法,可以重新赋值
- 命名规范
常量
- 命名约定:常量通常使用全大写字母,单词之间用下划线分隔,以增加可读性。
- 示例:
final int MAX_SPEED = 120;
变量
- 命名约定:变量名通常使用小写字母开头,遵循驼峰命名法。
- 示例:
int maxSpeed = 120;
- 内存分配
常量
- 内存优化:编译器会进行优化,对于常量通常在编译时就分配内存,并且可能在多处引用时只存储一份。
- 示例:
final String CONSTANT_STRING = "Hello, World!";
变量
- 动态分配:变量的内存分配是在运行时进行的,值的改变会导致内存中的内容改变。
- 示例:
String greeting = "Hello"; greeting = "Hi";
- 应用场景
常量
- 使用场景:用于表示程序中不会改变的值,如物理常数、配置参数等。
- 示例:
final int DAYS_IN_WEEK = 7;
变量
- 使用场景:用于表示程序中会变化的数据,如计数器、用户输入等。
- 示例:
int userAge = 25; userAge = 26; // 例如用户过了一年
6. 作用域
常量
- 作用域范围:常量可以是类常量(静态常量)或实例常量。
- 示例:
public class Constants { public static final double PI = 3.14159; // 类常量 }
变量
- 作用域范围:变量可以是局部变量、实例变量或类变量(静态变量)。
- 示例:
public class Example { public int instanceVar; // 实例变量 public static int staticVar; // 类变量 }
- 线程安全
常量
- 线程安全性:由于常量的不可变性,它们是天然线程安全的,不需要额外的同步机制。
- 示例:
public static final String APP_NAME = "MyApplication";
变量
- 线程安全性:变量在多线程环境下需要注意同步问题,确保线程安全。
- 示例:
public int sharedCounter;
结论
常量和变量在Java中有着明显的区别和不同的应用场景。常量用于表示不可变的数据,提高代码的可读性和维护性;变量用于存储和操作可变的数据,灵活性更强。理解并合理使用常量和变量,可以编写出更健壮、可维护的代码。
在Java中,内存泄露指的是程序中已经不再使用的对象没有被及时回收,继续占用内存,最终可能导致内存耗尽。虽然Java有垃圾回收机制(Garbage Collection,GC),但某些编程错误或设计缺陷仍可能导致内存泄露。以下是几种容易造成内存泄露的常见情况:
### 1. **静态集合类持有对象引用**
静态集合类(如 `HashMap`, `ArrayList`)会在整个应用程序生命周期内存活,如果将对象放入这些集合中,而没有及时清理,就会导致内存泄露。
```java
public class MemoryLeak {
private static List<Object> list = new ArrayList<>();
public void addToList(Object obj) {
list.add(obj);
}
}
解决方案:确保在对象不再需要时,从集合中移除或清理集合。
2. 未正确关闭的资源
未关闭的文件流、数据库连接、网络连接等,会导致资源无法释放,从而占用内存。
public void readFile(String fileName) throws IOException {
BufferedReader reader = new BufferedReader(new FileReader(fileName));
// 未关闭 reader,可能导致内存泄露
}
解决方案:使用 try-with-resources
语句,确保资源在使用后被正确关闭。
public void readFile(String fileName) throws IOException {
try (BufferedReader reader = new BufferedReader(new FileReader(fileName))) {
// 读取文件内容
}
}
3. 内部类和匿名类的非静态实例
非静态内部类和匿名类会隐式地持有外部类的引用,如果内部类对象的生命周期超过外部类对象,会导致外部类对象无法被垃圾回收。
public class OuterClass {
private class InnerClass {
// 内部类持有外部类的引用
}
}
解决方案:将内部类声明为静态类,避免持有外部类的引用。
public class OuterClass {
private static class InnerClass {
// 静态内部类不会持有外部类的引用
}
}
4. 缓存引起的内存泄露
缓存中的对象如果没有适时清理,会导致内存泄露。
public class Cache {
private Map<String, Object> cache = new HashMap<>();
public void addToCache(String key, Object value) {
cache.put(key, value);
}
}
解决方案:使用弱引用(WeakHashMap
)或定期清理缓存策略。
public class Cache {
private Map<String, Object> cache = new WeakHashMap<>();
public void addToCache(String key, Object value) {
cache.put(key, value);
}
}
5. 监听器和回调的未注销
注册的监听器或回调未及时注销,会导致对象引用无法释放。
public class EventSource {
private List<EventListener> listeners = new ArrayList<>();
public void addListener(EventListener listener) {
listeners.add(listener);
}
}
解决方案:在不再需要时,及时注销监听器或回调。
public class EventSource {
private List<EventListener> listeners = new ArrayList<>();
public void addListener(EventListener listener) {
listeners.add(listener);
}
public void removeListener(EventListener listener) {
listeners.remove(listener);
}
}
6. 长生命周期的对象持有短生命周期的对象引用
长生命周期的对象持有短生命周期的对象引用,会导致短生命周期对象无法及时被回收。
public class Manager {
private List<Employee> employees = new ArrayList<>();
public void addEmployee(Employee employee) {
employees.add(employee);
}
}
解决方案:尽量避免长生命周期对象持有短生命周期对象的引用,或及时清理引用。
public class Manager {
private List<WeakReference<Employee>> employees = new ArrayList<>();
public void addEmployee(Employee employee) {
employees.add(new WeakReference<>(employee));
}
}
7. 使用 ThreadLocal 造成的内存泄露
ThreadLocal 对象如果未及时移除,可能会导致内存泄露,尤其是在使用线程池时。
private static final ThreadLocal<SimpleDateFormat> formatter =
ThreadLocal.withInitial(() -> new SimpleDateFormat("yyyyMMdd"));
解决方案:在使用完 ThreadLocal 变量后,调用 remove()
方法。
private static final ThreadLocal<SimpleDateFormat> formatter =
ThreadLocal.withInitial(() -> new SimpleDateFormat("yyyyMMdd"));
// 使用后移除
formatter.remove();
结论
虽然Java的垃圾回收机制能够自动管理内存,但并非万能。开发者需要关注程序设计和代码实现中的细节,避免不必要的对象引用,及时清理资源,以防止内存泄露。通过合理设计和使用上述策略,可以有效减少内存泄露的风险。
## 拦截器和过滤器的区别
拦截器(Interceptor)和过滤器(Filter)是两种常见的处理机制,用于在请求处理过程中执行一些预处理或后处理的操作。尽管它们在某些情况下可以实现相似的功能,但它们的设计目标、应用场景和实现方式都有所不同。下面从多个角度对它们进行详细比较:
### 1. **定义和用途**
- **拦截器(Interceptor)**:
- **定义**:拦截器是基于面向切面编程(AOP)的一种实现方式,主要用于拦截方法调用或行为,在方法执行前后进行处理。
- **用途**:常用于业务逻辑层面,例如日志记录、权限校验、事务管理等。
- **过滤器(Filter)**:
- **定义**:过滤器是基于Servlet规范的一种机制,用于在请求到达Servlet之前或响应离开Servlet之后进行预处理和后处理。
- **用途**:常用于处理与HTTP请求/响应相关的任务,如字符编码设置、请求日志、输入验证、安全检查等。
### 2. **应用层级**
- **拦截器**:
- 作用于方法调用层级,可以用于控制器层、服务层等任意Java方法。
- 主要用于Spring MVC、Struts等框架中的方法调用拦截。
- **过滤器**:
- 作用于Web层级,处理HTTP请求和响应。
- 主要用于Servlet、JSP等Web组件的请求过滤。
### 3. **生命周期**
- **拦截器**:
- 生命周期由框架管理。拦截器在框架初始化时注册,方法调用前后执行特定代码。
- 执行顺序可通过配置顺序控制。
- **过滤器**:
- 生命周期由Servlet容器管理。在Web应用启动时初始化,销毁时清理。
- 执行顺序通过`web.xml`或注解配置,按照配置顺序执行。
### 4. **实现方式**
- **拦截器**:
- 在Spring中,拦截器实现`HandlerInterceptor`接口。
- 示例:
```java
public class MyInterceptor implements HandlerInterceptor {
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
// 前处理逻辑
return true; // 返回true继续处理请求,返回false终止请求
}
@Override
public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {
// 后处理逻辑
}
@Override
public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
// 完成后处理逻辑
}
}
```
- **过滤器**:
- 过滤器实现`javax.servlet.Filter`接口。
- 示例:
```java
public class MyFilter implements Filter {
@Override
public void init(FilterConfig filterConfig) throws ServletException {
// 初始化逻辑
}
@Override
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
// 预处理逻辑
chain.doFilter(request, response); // 继续处理请求
// 后处理逻辑
}
@Override
public void destroy() {
// 清理资源逻辑
}
}
```
### 5. **配置方式**
- **拦截器**:
- 在Spring中,通过配置类或XML文件进行配置。
- 示例(Java配置):
```java
@Configuration
public class WebConfig implements WebMvcConfigurer {
@Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(new MyInterceptor()).addPathPatterns("/**");
}
}
```
- **过滤器**:
- 通过`web.xml`或注解进行配置。
- 示例(注解配置):
```java
@WebFilter(filterName = "myFilter", urlPatterns = "/*")
public class MyFilter implements Filter {
// 过滤器实现代码
}
```
### 6. **适用场景**
- **拦截器**:
- 适用于需要处理控制器或服务层方法调用的场景,如权限验证、日志记录、事务管理等。
- 更适合业务逻辑处理,紧密结合应用框架。
- **过滤器**:
- 适用于需要处理HTTP请求和响应的场景,如请求日志、跨域处理、字符编码设置、安全检查等。
- 更适合Web应用层的处理,紧密结合Servlet容器。
### 总结
拦截器和过滤器各有其适用的场景和特点。拦截器主要用于业务逻辑层面的方法调用拦截,而过滤器则主要用于Web层面的HTTP请求和响应处理。了解两者的区别和适用场景,可以更好地选择和应用它们,以实现预期的功能和性能优化。