抱歉,您的浏览器无法访问本站
本页面需要浏览器支持(启用)JavaScript
了解详情 >

Q:java并发编程并发的是线程还是进程?

A:线程,不过说的很紊乱,讲了很多没必要讲的东西

以下是gpt回答

解释:

  1. 线程 是程序中的一个执行单元,线程是轻量级的,它们共享进程的资源,如内存和文件句柄。在Java中,线程是通过实现 Runnable 接口或继承 Thread 类来创建的。
  2. 进程 是操作系统分配资源的最小单位,每个进程都有自己独立的内存空间。Java程序通常运行在一个进程中,而该进程内可能有多个线程。

并发的定义:

  • 并发 指的是多个任务的执行在时间上有重叠,而不一定是同时进行。通过多线程并发执行,Java能够在多核处理器上高效地利用多个CPU核心,但并发本身并不等同于并行。并行是指多个任务在同一时刻执行。

总结:

  • Java并发编程中,“并发”是指 多个线程 的执行。
  • 如果涉及多个进程,通常称之为 多进程 编程,而不属于并发的范畴。

Q:进程和线程的区别

A:一个进程可能就是会包含多个线程,线程可以理解为轻量级的进程,上下文切换这个开销一般是比较小的,线程可以共享这个进程的一些资源比如说内存空间(内存空间埋雷了)


Q:那你觉得你对于他们的底层结构了解吗,比如说你刚才提到了共享内存那你共享的资源嘛对吧那你能说一下比如说我启动了一个那个进程然后那进程跟线程之间应该是如何去申请这个资源以及实现资源共享呢。后续又补充了一点,比如说创建一个进程然后这个资源我是怎么申请的以及线程去怎么去共享这些资源的。

A:的一塌糊涂


Q:线程池的作用以及实现原理。

A:复用线程,实现原理不会(其实就是线程池处理任务的流程,不知道是这个题目,背了八股也没用上😭😭😭😭

以下是gpt回答

线程池的实现原理

线程池的核心思想是任务提交-线程复用。以下是线程池的基本组成和工作原理:

1. 基本组成

  1. 任务队列(Task Queue)
    • 一个线程安全的队列,用于存储提交但尚未被执行的任务。
    • 任务是实现了某种接口(如 RunnableCallable)的对象。
  2. 工作线程(Worker Threads)
    • 线程池中实际执行任务的线程集合。线程池在初始化时会创建一定数量的线程,并在任务提交后复用这些线程执行任务。
  3. 线程管理器
    • 管理线程的生命周期,维护线程池中线程的数量。
    • 根据配置动态增加或减少线程数(如扩展或缩减线程池大小)。
  4. 任务提交接口
    • 提供给用户用于提交任务的方法,比如 execute()submit()
  5. 任务调度器
    • 从任务队列中获取任务并分配给工作线程。
    • 如果任务队列为空,工作线程会进入等待状态。

2. 线程池的工作流程

  1. 线程池初始化
    • 根据配置,线程池创建一定数量的工作线程,并处于等待任务的状态。
  2. 任务提交
    • 用户通过线程池的接口(如 execute()submit())提交任务,任务被添加到任务队列中。
  3. 任务调度
    • 线程池中的工作线程会从任务队列中取出任务进行执行。
    • 如果所有工作线程都在忙,任务会在任务队列中排队等待。
  4. 任务执行
    • 工作线程执行完任务后,线程不会被销毁,而是返回线程池继续等待新的任务。
  5. 动态扩展(可选)
    • 如果任务队列过长且所有线程都在忙,线程池可以动态创建新的线程来处理任务(受最大线程数限制)。
  6. 线程回收
    • 如果线程池中某些线程长时间没有任务可执行,线程池可以销毁这些空闲线程以节省资源。

Q、如果没有锁机制的话在并发编程的场景会出现什么问题。

A:数据的一致性可能出现问题(问我还有没有补充,我也没想到其它的了

以下是在网上搜的,并发编程需要加锁的时候,如果就不加会怎么样?

  1. 数据不一致:多个线程同时访问和修改共享资源时,如果没有加锁,可能会导致数据竞争,即一个线程在读取数据的同时,另一个线程修改了数据,从而导致最终的数据状态与预期不符。例如,在多线程环境下,多个线程同时对同一个账户余额进行操作,如果不加锁,可能会出现余额被重复扣款或重复加款的情况。
  2. 竞态条件:竞态条件是指在多线程环境中,由于线程调度的不确定性,导致程序的行为依赖于不可预测的执行顺序。如果不加锁,可能会导致程序在某些情况下出现不可预期的行为,如死锁、饥饿等问题。
  3. 线程安全问题:在多线程编程中,多个线程可能会同时访问共享资源,这很容易导致数据的不一致性和竞态条件。如果不加锁,可能会导致线程安全问题,影响程序的正确性和稳定性。
  4. 死锁风险:死锁是指两个或多个线程互相等待对方释放资源,导致所有线程都无法继续执行。如果不加锁,可能会增加死锁的风险,尤其是在复杂的并发场景中。
  5. 难以调试:在多线程环境中,如果不加锁,可能会导致难以调试的问题。由于线程的执行顺序是不可预测的,错误可能在某些特定的执行路径下才会出现,这使得调试变得非常困难。

Q:项目:怎么实现优惠券的秒杀

A:这个基本答得是那么回事,就是乐观锁的cas


Q:什么叫乐观锁?为什么不用悲观锁?

A:适用读多写少,然后说了一下cas的实现(感觉都没答到点子上😅😅);用悲观锁就是影响性能,因为会频繁的更新数据


Q:select语句的执行过程

A:(这个答得应该大差不差,唯一画蛇添足的一点就是把废弃的那个缓存器也答上了,又给后面埋雷了😅😅😅😅)连接器——语法器——优化器——执行器


Q:缓存的粒度是什么?或者说它是怎么缓存的,缓存的东西是什么

A:直接说不清楚,缓存的应该是语句吧。然后问如果缓存了SELECT A = 1再来执行SELECT A = 1 AND B = 1能用到缓存吗?猜的能

以下来自gpt

存储常用或最近使用的数据和查询结果,提高了查询性能,减少了对底层存储的访问。 存储查询的完整结果集,下一次遇到相同的查询语句时,直接返回结果。 适用于简单的 SELECT 查询,尤其是少更新、多读取的场景。

默认情况下,SELECT A = 1 的缓存不能直接用于 SELECT A = 1 AND B = 1,因为两者查询条件和结果集不同。


Q:SELECT语句想做查询优化,从哪些方面去优化?

A:答了索引,避免查询不必要的列,数据量过大时从分表三点,问还有补充没,

以下来自gpt

优化 SELECT 语句的方法包括:

  1. 从表结构设计入手,建立合理的索引,优化字段类型,必要时分表或分区。
  2. 改进查询语句,减少不必要的字段、行和表,避免复杂计算。
  3. 提高数据访问效率,利用缓存和分页优化减少查询开销。
  4. 分析查询执行计划,通过工具发现潜在问题并调整查询结构和索引。

Q:索引的底层结构是啥?

A:说了B+树,然后问只有B+树么?还有其它结构吗,然后又回答了还有哈希


Q:从数据结构的角度来说,能影响B+树的查询性能的,是这个树的什么

A:树高

以下来自gpt


Q:我们怎么影响这个树的高度呢?通过哪些方式让这个树的高度更扁平一点?

A:不知道,瞎几把答了

以下来自gpt

树高主要受以下因素影响:

  1. 节点扇出数(由节点大小和键值大小决定)。
  2. 数据分布是否均匀
  3. 数据量和磁盘块的大小
  4. 键值的大小和排序

问答18min。手撕环节。差不多三分钟说了一下思路。撕了差不多20min才跑通测试用例,

Q:题目:给中缀表达式转后缀表达式

思路:

  • 遇到操作数(数字或变量):直接输出到结果中。
  • 遇到左括号 (:直接压入操作符栈。
  • 遇到右括号 )
    • 弹出栈顶的操作符并加入结果,直到遇到左括号(左括号本身不加入结果)。
  • 遇到运算符(如 +, -, *, /):
    • 将栈顶优先级高于或等于当前操作符的运算符弹出并加入结果。
    • 然后将当前操作符压栈。
  • 遍历结束后:将栈中剩余的运算符依次弹出并加入结果。

算法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
// 定义运算符优先级
private static final Map<Character, Integer> precedence = new HashMap<>();
static {
precedence.put('+', 1);
precedence.put('-', 1);
precedence.put('*', 2);
precedence.put('/', 2);
precedence.put('(', 0); // 左括号优先级最低
}

public static String infixToPostfix(String infix) {
// 栈:存储操作符
Stack<Character> operatorStack = new Stack<>();
// 结果字符串
StringBuilder postfix = new StringBuilder();

// 遍历表达式中的每个字符
for (int i = 0; i < infix.length(); i++) {
char ch = infix.charAt(i);

// 如果是操作数(字母或数字),直接加入结果
if (Character.isLetterOrDigit(ch)) {
postfix.append(ch);
}
// 如果是左括号,压入栈
else if (ch == '(') {
operatorStack.push(ch);
}
// 如果是右括号,弹出运算符直到遇到左括号
else if (ch == ')') {
while (!operatorStack.isEmpty() && operatorStack.peek() != '(') {
postfix.append(operatorStack.pop());
}
if (!operatorStack.isEmpty() && operatorStack.peek() == '(') {
operatorStack.pop(); // 弹出左括号
}
}
// 如果是运算符
else {
// 弹出栈顶优先级大于等于当前运算符的运算符
while (!operatorStack.isEmpty() && precedence.get(operatorStack.peek()) >= precedence.get(ch)) {
postfix.append(operatorStack.pop());
}
// 当前运算符压入栈
operatorStack.push(ch);
}
}

// 遍历结束后,弹出栈中剩余的运算符
while (!operatorStack.isEmpty()) {
postfix.append(operatorStack.pop());
}

// 返回后缀表达式
return postfix.toString();
}

评论