1、最近项目使用的技术架构;
略
2、多线程的参数有哪些;
一、线程池关键参数详解
Java线程池的核心实现是ThreadPoolExecutor,其构造函数包含7个关键参数:
public ThreadPoolExecutor( int corePoolSize, // 核心线程数 int maximumPoolSize, // 最大线程数 long keepAliveTime, // 空闲线程存活时间 TimeUnit unit, // 时间单位 BlockingQueue<Runnable> workQueue, // 任务队列 ThreadFactory threadFactory, // 线程工厂 RejectedExecutionHandler handler // 拒绝策略 );
1. 线程数控制参数
corePoolSize(核心线程数)
- 线程池初始化时默认不会创建线程,直到有任务提交
- 当线程数 < corePoolSize时,即使有空闲线程也会创建新线程执行任务
- 可通过prestartAllCoreThreads()预创建所有核心线程
maximumPoolSize(最大线程数)
- 线程池允许的最大线程数量
- 当队列满且线程数 < maximumPoolSize时,会创建新线程执行任务
keepAliveTime & unit(空闲线程存活时间)
- 非核心线程空闲超过此时间会被销毁
- 通过allowCoreThreadTimeOut(true)可使核心线程同样受此参数影响
2. 任务队列(BlockingQueue)
SynchronousQueue
- 不存储元素的队列,每个插入操作必须等待另一个线程的移除操作
- Executors.newCachedThreadPool()默认使用此队列
- 优点:吞吐量极高;缺点:可能创建大量线程导致OOM
LinkedBlockingQueue
- 基于链表实现的无界队列(默认容量为Integer.MAX_VALUE)
- Executors.newFixedThreadPool()默认使用此队列
- 风险:任务堆积时可能导致OOM
ArrayBlockingQueue
- 基于数组实现的有界队列,必须指定容量
- 通过合理设置容量和拒绝策略,可有效防止资源耗尽
PriorityBlockingQueue
- 支持优先级排序的无界队列,需任务实现Comparable接口
3. 拒绝策略(RejectedExecutionHandler)
当队列满且线程数达到maximumPoolSize时触发:
- AbortPolicy(默认)
直接抛出RejectedExecutionException,阻止系统正常运行
- CallerRunsPolicy
由调用线程(提交任务的线程)直接执行该任务,降低提交速率
- DiscardPolicy
默默丢弃任务,不抛出任何异常
- DiscardOldestPolicy
丢弃队列中最老的任务,尝试重新提交当前任务
二、线程池性能优化策略
1. 参数配置优化
CPU密集型任务
线程数 ≈ CPU核心数 + 1
int corePoolSize = Runtime.getRuntime().availableProcessors() + 1; new ThreadPoolExecutor( corePoolSize, corePoolSize, 0L, TimeUnit.MILLISECONDS, new ArrayBlockingQueue<>(100) );
IO密集型任务
线程数 ≈ CPU核心数 × (1 + 平均等待时间/平均处理时间)
int corePoolSize = Runtime.getRuntime().availableProcessors() * 2; new ThreadPoolExecutor( corePoolSize, corePoolSize * 2, 60L, TimeUnit.SECONDS, new LinkedBlockingQueue<>(1000) );
混合型任务
将任务拆分为CPU密集型和IO密集型,分别使用不同线程池处理
2. 队列选择策略
任务执行时间短、吞吐量高 → SynchronousQueue
任务执行时间长、需要限制线程数 → ArrayBlockingQueue
任务量稳定、避免任务丢弃 → LinkedBlockingQueue(需控制容量)
3. 拒绝策略选择
关键任务 → CallerRunsPolicy(避免数据丢失)
非关键任务 → DiscardPolicy或DiscardOldestPolicy
4. 线程工厂定制
设置线程名称前缀,便于问题定位
设置守护线程,避免影响JVM退出
ThreadFactory factory = new ThreadFactoryBuilder() .setNameFormat("custom-pool-%d") .setDaemon(true) .build();
5. 动态调整参数
通过JMX或配置中心动态调整线程池参数:
// 增加核心线程数 executor.setCorePoolSize(newCoreSize); // 调整队列容量(需自定义可调整容量的队列) customQueue.setCapacity(newCapacity);3、java 多线程需要返回值,有哪些方式
在Java中,实现多线程并返回结果有多种方式,每种方式有其适用场景。以下是几种常见的方法:
1. 使用Callable和Future
Callable接口类似于Runnable,但它可以返回一个结果并且可以抛出异常。你可以使用ExecutorService来执行Callable任务,并通过Future获取结果。
import java.util.concurrent.*; public class CallableExample { public static void main(String[] args) throws ExecutionException, InterruptedException { ExecutorService executor = Executors.newFixedThreadPool(2); Callable<String> task = () -> { // 模拟耗时操作 Thread.sleep(1000); return "Task result"; }; Future<String> future = executor.submit(task); System.out.println("Task result: " + future.get()); // 获取结果 executor.shutdown(); } }2. 使用CompletableFuture
CompletableFuture提供了更灵活的异步编程方式,支持链式调用和组合多个异步操作。
import java.util.concurrent.*; public class CompletableFutureExample { public static void main(String[] args) throws ExecutionException, InterruptedException { ExecutorService executor = Executors.newFixedThreadPool(2); CompletableFuture<String> future = CompletableFuture.supplyAsync(() -> { try { Thread.sleep(1000); // 模拟耗时操作 } catch (InterruptedException e) { Thread.currentThread().interrupt(); // 重新设置中断状态 } return "Task result"; }, executor); System.out.println("Task result: " + future.get()); // 获取结果 executor.shutdown(); } }3. 使用AtomicReference或自定义容器类在Runnable中共享结果
如果你不想使用Callable或Future,可以通过共享变量(如AtomicReference)在多个线程之间传递结果。但这种方式通常不推荐,因为它违背了多线程设计的最佳实践(即避免共享可变状态)。更好的方式是使用上述的Callable和Future。
4. 使用线程局部变量(ThreadLocal)传递结果(不推荐)
虽然可以使用ThreadLocal在每个线程中存储结果,但这同样不是最佳实践,因为它限制了结果的共享和并发性。通常,这种方法只在特定场景下使用,例如在每个线程需要独立处理但又需要访问某些共享数据时。
总结:
推荐使用Callable和Future或者CompletableFuture来实现多线程并返回结果,因为这些方法提供了更好的错误处理、灵活性以及与Java并发工具库的集成。尽量避免使用共享可变状态的方法,比如通过共享变量或线程局部变量传递结果。这些方法虽然技术上可行,但会使代码更难理解和维护。
4、GC机制有哪些;
Java 的垃圾回收(GC)机制是 JVM 自动管理内存的核心功能,主要目标是回收不再使用的对象,防止内存泄漏和溢出。根据当前权威公开资料(截至 2026 年 4 月),Java GC 机制主要包括以下几方面:
一、判断对象是否可回收的算法
引用计数法
- 每个对象维护一个引用计数器,被引用时 +1,引用失效时 -1。
- 缺点:无法解决循环引用问题,因此 Java 虚拟机未采用此算法 12。
可达性分析算法(主流)
- 以 GC Roots 为起点,向下搜索引用链。若对象与 GC Roots 无任何引用链相连,则视为不可达,可被回收。
- GC Roots 包括:
- 虚拟机栈(栈帧中的本地变量)引用的对象
- 方法区中类静态属性引用的对象
- 方法区中常量引用的对象
- 本地方法栈中 JNI(Native 方法)引用的对象
二、Java 中的四种引用类型(强度递减)
强引用(Strong Reference)
- 如
Object obj = new Object(),只要强引用存在,GC 永远不会回收该对象。
- 如
软引用(Soft Reference)
- 描述“有用但非必需”的对象。
- 内存不足前会保留,发生内存溢出异常前才会被回收。适用于缓存。
弱引用(Weak Reference)
- 非必需对象,仅存活到下一次 GC 之前,无论内存是否充足。
虚引用(Phantom Reference)
- 最弱引用,无法通过它获取对象实例。
- 唯一作用是在对象被回收时收到系统通知,必须配合
ReferenceQueue使用。
三、对象回收的“两次标记”流程
- 第一次标记:可达性分析后发现无 GC Roots 引用链,进行筛选——判断是否有必要执行
finalize()方法。 - 第二次标记:若
finalize()未被调用过且被重写,则放入 F-Queue 队列,由低优先级 Finalizer 线程执行。- 若在
finalize()中重新与 GC Roots 建立关联(如this赋值给静态变量),可“自救”; - 但
finalize()仅执行一次,第二次回收时直接清除 。
- 若在
⚠️ 注意:
finalize()方法已被标记为 deprecated(JDK 9 起),不推荐使用,应改用try-finally或Cleaner机制。
四、常见的垃圾回收算法
标记-清除(Mark-Sweep)
- 标记所有存活对象,清除未标记的。
- 缺点:效率低,产生内存碎片 。
复制(Copying)
- 将内存分为两块,存活对象复制到另一块,清空原块。
- 优点:无碎片;缺点:内存利用率仅 50%(HotSpot 优化为 Eden:Survivor = 8:1:1)。
标记-整理(Mark-Compact)
- 标记后将所有存活对象向一端移动,清理边界外内存。
- 优点:避免碎片,适合老年代 。
分代收集(Generational Collection)
- 新生代:对象朝生夕死,采用复制算法
- 老年代:对象存活率高,采用标记-清除或标记-整理
五、主要垃圾收集器(HotSpot 虚拟机)
| 收集器 | 区域 | 算法 | 特点 |
|---|---|---|---|
| Serial | 新生代 | 复制 | 单线程,Client 模式默认 |
| ParNew | 新生代 | 复制 | Serial 多线程版,可与 CMS 配合 |
| Parallel Scavenge | 新生代 | 复制 | 关注吞吐量,可控制停顿时间 |
| Parallel Old | 老年代 | 标记-整理 | Parallel Scavenge 的老年代搭档 |
| CMS | 老年代 | 标记-清除 | 低停顿,已废弃(JDK 9+) |
| G1 | 整堆 | 分区 + 复制+整理 | 支持 Region 划分,可预测停顿,Java 9+ 默认 511 |
G1 是目前主流生产环境推荐的收集器,适用于大堆(>4GB)和低延迟场景 511。
六、内存分配策略
- 对象优先在 Eden 区 分配
- 大对象直接进入 老年代(
-XX:PretenureSizeThreshold) - 长期存活对象晋升至老年代(年龄阈值默认 15,可调
-XX:MaxTenuringThreshold) - 动态年龄判断:Survivor 中相同年龄对象总和 > 一半时,直接晋升老年代
- 空间分配担保:Minor GC 前检查老年代剩余空间是否足够容纳新生代所有存活对象
5、spring事务@Transactional什么情况下会失效;
在使用Spring框架进行事务管理时,@Transactional注解确实非常强大,但它也有一些情况下可能不会按预期工作。以下是一些常见的导致@Transactional注解失效的情况:
代理方式不正确:
- 基于接口的代理:确保你的类是基于接口的。只有基于接口的代理才会通过Spring AOP代理事务。如果你的类是一个类(没有实现接口),你需要使用类代理方式,这通常通过配置来实现,比如在XML配置中设置
<tx:annotation-driven proxy-target-class="true"/>。 - 类代理:如果你使用的是类而不是接口,确保你在
@EnableTransactionManagement注解中或者在你的配置类中设置了proxyTargetClass=true。
- 基于接口的代理:确保你的类是基于接口的。只有基于接口的代理才会通过Spring AOP代理事务。如果你的类是一个类(没有实现接口),你需要使用类代理方式,这通常通过配置来实现,比如在XML配置中设置
方法必须是public:
@Transactional注解的方法必须是public的。如果你在一个public方法中调用一个非public方法(即使是同一个类中的),事务将不会应用到那个非public方法上。
内部调用问题:
- 如果你在一个被
@Transactional注解的方法内部调用另一个方法,而那个方法没有被@Transactional注解,事务将不会应用到那个内部调用的方法上。例如,在同一个类中调用另一个方法时,除非那个方法也是事务性的,否则事务不会传播。
- 如果你在一个被
异常类型:
- 如果你抛出的异常没有被
@Transactional注解的rollbackFor或rollbackForClassName属性捕获,事务可能不会回滚。例如,如果方法抛出了RuntimeException之外的异常,而你只配置了rollbackFor = Exception.class,那么只有Exception及其子类的异常才会导致回滚。
- 如果你抛出的异常没有被
事务管理器配置:
- 确保你的应用配置了正确的事务管理器,并且
@Transactional注解指向了正确的事务管理器。在Spring配置中,你需要确保<tx:annotation-driven/>指向了正确的事务管理器。
- 确保你的应用配置了正确的事务管理器,并且
数据访问层框架支持:
- 确保你使用的数据访问层框架(如JPA, Hibernate, JdbcTemplate等)被Spring正确支持。例如,对于JPA和Hibernate,你需要确保EntityManager或Session被正确地注入和使用。
配置问题:
- 确保你的Spring配置(无论是XML配置还是Java配置)正确设置了事务管理器和AOP代理。
解决方法示例:
确保使用基于接口的代理:
@Configuration @EnableTransactionManagement(proxyTargetClass = true) public class AppConfig { // 配置数据源、事务管理器等 }确保方法为public:
public class MyService { @Transactional public void publicMethod() { // 方法内容 } private void privateMethod() { // 不会被事务管理的方法 } }正确配置事务回滚:
@Transactional(rollbackFor = Exception.class) public void myMethod() { // 方法内容,抛出Exception将导致回滚 }通过检查和解决以上问题,通常可以解决@Transactional注解失效的情况。
6、vue 前端项目父子间怎么传值
在Vue.js中,父子组件间的传值可以通过多种方式进行。以下是几种常见的方法:
1. 使用props传递数据
父组件向子组件传值:
- 在子组件中定义
props。 - 在父组件的模板中,通过属性绑定将数据传递给子组件。
子组件(ChildComponent.vue):
<template> <div> <p>{{ message }}</p> </div> </template> <script> export default { props: ['message'] } </script>父组件(ParentComponent.vue):
<template> <ChildComponent :message="parentMessage" /> </template> <script> import ChildComponent from './ChildComponent.vue'; export default { components: { ChildComponent }, data() { return { parentMessage: 'Hello from parent!' } } } </script>2. 使用事件传递数据(子组件向父组件)
子组件向父组件传值:
- 在子组件中,使用
$emit触发一个事件。 - 在父组件中监听这个事件并接收数据。
子组件(ChildComponent.vue):
<template> <button @click="sendToParent">Send to Parent</button> </template> <script> export default { methods: { sendToParent() { this.$emit('updateMessage', 'Hello from child!'); } } } </script>父组件(ParentComponent.vue):
<template> <ChildComponent @updateMessage="handleMessage" /> </template> <script> import ChildComponent from './ChildComponent.vue'; export default { components: { ChildComponent }, methods: { handleMessage(message) { console.log(message); // 处理从子组件接收的数据 } } } </script>3. 使用Vuex进行状态管理(适用于复杂状态管理)
如果你有多个组件需要共享状态,或者父子组件层级较深,使用Vuex是一个好选择。
- 安装并设置Vuex。
- 在Vuex中定义state、mutations和actions。
- 在需要数据的组件中,通过
this.$store.state.someProperty或通过mapState辅助函数来访问状态。在需要修改状态时,通过this.$store.commit('someMutation')或this.$store.dispatch('someAction')。
安装Vuex:
npm install vuex@next --save # Vue 3使用此命令,Vue 2使用`vuex`而不是`vuex@next`。设置Vuex:
// store.js 或 store/index.js import { createStore } from 'vuex'; export default createStore({ state: { message: '' }, mutations: { setMessage(state, message) { state.message = message; } }, actions: { updateMessage({ commit }, message) { commit('setMessage', message); } } });在Vue组件中使用Vuex:
// 在组件中引用store并使用它。例如,在ParentComponent或ChildComponent中。 import { useStore } from 'vuex'; // Vue 3使用此导入方式,Vue 2使用`import store from './store'`。 并在组件中使用`this.$store`。 确保在根实例中提供了store。 例如,在main.js或main.ts中:`createApp(App).use(store).mount('#app');`。 然后在任何子组件中通过`this.$store`访问。 如果你使用的是组合式API(Composition API),可以使用`const store = useStore();`。 然后在setup函数中使用它。例如:`store.commit('setMessage', 'Hello from child!');`。 对于选项式API(Options API),仍然使用`this.$store`。例如:`this.$store.commit('setMessage', 'Hello from child!');`。 确保在setup函数外部或在任何非setup函数中使用this.$store来访问Vuex store。对于组合7、前端写过哪些公共组件
8、两张表一样,A表比B表多一条数据,怎么查询出来B表少的那条数据;
在MySQL中,如果你想要找出两张表(A表和B表)之间的差异,特别是找出B表缺少的那条数据,你可以使用以下几种方法:
方法1:使用EXCEPT(MySQL 8.0及以上版本)
如果你的MySQL版本是8.0或以上,可以使用EXCEPT关键字来找出B表缺少的数据。这种方法类似于SQL Server中的EXCEPT。
SELECT * FROM A EXCEPT SELECT * FROM B;方法2:使用NOT IN
如果你使用的是MySQL 8.0以下的版本,可以使用NOT IN来实现相同的效果,但这种方法在某些情况下可能效率不高,特别是在处理大量数据时。
SELECT * FROM A WHERE (A.column1, A.column2, ...) NOT IN ( SELECT B.column1, B.column2, ... FROM B );你需要将column1,column2, ...替换为你的具体列名。
方法3:使用FULL OUTER JOIN然后过滤
使用FULL OUTER JOIN可以展示所有在A表和B表中都存在的行,以及那些只在A表或B表中存在的行。然后你可以通过选择那些在B表中没有的行来找出差异。
SELECT A.* FROM A LEFT JOIN B ON A.column1 = B.column1 AND A.column2 = B.column2 AND ... WHERE B.column1 IS NULL; -- 这里可以根据你的主键或唯一标识列来选择条件或者反过来,如果你想要找出A表和B表共同的记录之外的记录:
SELECT A.* FROM A LEFT JOIN B ON A.column1 = B.column1 AND A.column2 = B.column2 AND ... WHERE B.column1 IS NULL UNION ALL SELECT B.* FROM B LEFT JOIN A ON A.column1 = B.column1 AND A.column2 = B.column2 AND ... WHERE A.column1 IS NULL;这种方法可以找出所有在A或B中独有的记录。
方法4:使用MINUS(Oracle的语法)
虽然这不是MySQL的语法,但如果你熟悉Oracle,可以这样写:
SELECT * FROM A MINUS SELECT * FROM B;在MySQL中,你可以通过上述方法实现类似的效果。
总结:
对于大多数情况,方法3(使用FULL OUTER JOIN然后过滤)是比较通用和灵活的方法。根据你的具体需求(比如是否需要找出所有独有的记录),你可以选择合适的方法。如果你使用的是MySQL 8.0或以上版本,推荐使用EXCEPT方法,因为它更直观且易于理解。