基础

  • 进程和线程的区别
  • 定义:进程是程序的执行实例,是资源分配的基本单位,具有独立的内存空间和系统资源。线程是进程中的一个执行单元,是CPU调度的基本单位,共享进程的内存空间和系统资源。
  • 资源占用:每个进程都有自己的独立内存空间和系统资源,包括文件句柄、网络连接等。而线程共享所属进程的内存空间和系统资源,可以直接访问进程的全局变量和数据结构
  • 切换开销:由于进程拥有独立的内存空间和系统资源,进程之间的切换开销较大。而线程之间的切换开销较小,因为它们共享相同的内存空间和系统资源
  • 通信方式:进程之间通信需要使用特定的机制,如管道、消息队列、共享内存等。而线程之间通信更加方便,可以通过共享内存、全局变量等直接进行数据交换
  • 并发性:由于线程共享进程的资源,多个线程可以同时执行不同的任务,从而提高了并发性。而进程之间的并发性较低,需要通过进程间通信来实现协作。

总的来说,进程和线程是操作系统中不同层次的执行单位。进程是资源分配的基本单位,而线程是CPU调度的基本单位。进程之间相互独立,线程之间共享资源。线程的切换开销较小,通信更加方便,适合用于并发执行任务。而进程之间的切换开销较大,通信需要特定的机制,适合用于隔离和保护不同的应用程序。

  • synchronized原理

synchronized是Java中用于实现线程同步的关键字,它可以应用于方法或代码块。

  • 对象锁:synchronized使用对象级别的锁来实现线程同步。每个Java对象都有一个与之关联的监视器锁(也称为内置锁或互斥锁)。当线程进入synchronized代码块时,它会尝试获取该对象的锁
  • 互斥性:一旦某个线程获得对象的锁,其他线程就无法同时获得相同对象的锁。这意味着其他线程必须等待当前线程释放锁后才能继续执行
  • 可重入性:同一个线程可以多次获取同一个对象的锁,而不会被自己所持有的锁所阻塞。这种机制称为可重入性,它避免了死锁的发生
  • 内存可见性:synchronized不仅保证了互斥性,还保证了对共享变量的修改对其他线程可见。当一个线程释放锁时,会将对共享变量的修改刷新到主内存中,使得其他线程能够看到最新的值
  • 锁的释放:当线程执行完synchronized代码块或方法时,会释放对象的锁,让其他线程有机会获取锁并执行

需要注意的是,synchronized关键字只能实现基本的线程同步,对于复杂的同步需求,可能需要使用更高级的并发工具,如Lock接口、Condition接口、Semaphore等

使用过程

使用synchronized之后,会在编译之后在同步的代码块前后加上monitorenter和monitorexit字节码指令,它依赖操作系统底层互斥锁实现。它的作用主要就是实现原子性操作和解决共享变量的内存可见性问题。

执行monitorenter指令时会尝试获取对象锁,如果对象没有被锁定或者已经获得了锁,锁的计数器+1。此时其它竞争锁的线程则会进入等待队列中。

执行monitorexit指令时则会把计数器-1,当计数器值为0时,则锁释放,处于等待队列中的线程再继续竞争锁。

synchronized是排它锁,当一个线程获得锁之后,其他线程必须等待该线程释放锁后才能获得锁,而且由于Java中的线程和操作系统原生线程是一一对应的,线程被阻塞或者唤醒时会从用户态切换到内核态,这种转换非常消耗性能。

从内存语义来说,加锁的过程会清除工作内存中的共享变量,再从主内存读取,而释放锁的过程则是将工作内存中的共享变量写回主内存。

synchronized实际上有两个队列waitSet和entryList。

1.当多个线程进入同步代码块时,首先进入entryList

2.有一个进程获取到monitor锁后,就赋值给当前线程,并且计数器+1

3.如果线程调用wait方法,将释放锁,当前线程置为null,计数器-1,同时进入waitSet等待被唤醒,调用notify或者notifyAll之后又会进入entryList竞争锁

4.如果线程执行完毕,同样释放锁,计数器-1,当前线程置为null

常见面试题_结果集

  • 锁的优化机制

从jdk1.6版本之后,synchronized本身也在不断优化锁的机制,有些情况下它并不会是一个很重量级的锁了。优化机制包括自适应锁、自旋锁、锁消除、锁粗化、轻量级锁和偏向锁。

锁的状态从低到高依次为无锁-》偏向锁-》轻量级锁-》重量级锁,升级的过程就是从低到高,降级在一定条件也是有可能发生的。

  • 自旋锁:由于大部分时候,锁被占用的时间很短,共享变量的锁定时间也很短,所以没有必要挂起线程,用户态和内核态的来回上下文切换严重影响性能。自旋的概念就是让线程执行一个忙循环,可以理解为就是啥也不干,防止从用户态转入内核态,自旋锁可以通过设置-XX:+UseSpining来开启,自旋的默认次数是10次,可以使用-XX:PreBlockSpin设置。
  • 自适应锁:自适应锁就是自适应的自旋锁,自旋的时间不是固定时间,而是由前一次在同一个锁上的自旋时间和锁的持有者状态来决定。
  • 锁消除:锁消除指的是JVM检测到一些同步的代码块,完全不存在数据竞争的场景,也就是不需要加锁,就会进行锁消除。
  • 锁粗化:锁粗化指的是有很多操作都是对同一个对象进行加锁,就会把锁的同步范围扩展到整个操作序列之外。
  • 偏向锁:当线程访问同步块获取锁时,会在对象头和栈帧中的锁记录里存储偏向锁的线程id,之后这个线程再次进入同步块时都不需要CAS来加锁和解锁了,偏向锁会永远偏向第一个获得锁的线程,如果后续没有其他线程获得过这个锁,持有锁的线程就永远不需要进行同步,反之,当有其他线程竞争偏向锁时,持有偏向锁的线程就会释放偏向锁。可以用设置-XX:+UseBiasedLocking开启偏向锁。
  • 轻量级锁:JVM的对象的对象头中包含有一些锁的标志位,代码进入同步块的时候,JVM将会使用CAS方式来尝试获取锁,如果更新成功则会把对象头中的状态位标记为轻量级锁,如果更新失败,当前线程就尝试自旋来获得锁。

简单点说,偏向锁就是通过对象头的偏向线程id来对比,甚至都不需要CAS了,而轻量级锁主要就是通过CAS修改对象头锁记录和自旋来实现,重量级锁则是除了拥有锁的线程其他全部阻塞。

常见面试题_数据_02

  • 对象头具体包含哪些内容

在我们常用的Hotspot虚拟机中,对象在内存中布局实际包含3个部分:

  1. 对象头
  2. 实例数据
  3. 对齐填充

而对象头包含两部分内容,Mark Work中的内容会随着锁标志位而发生变化,所以只说存储结构就好了。

  1. 对象自身运行时所需的数据,也被称为Mark Word,也就是用于轻量级锁和偏向锁的关键点。具体的内容包含对象的hashcode、分代年龄、轻量级锁指针、重量级锁指针、GC标记、偏向锁线程ID、偏向锁时间戳。
  2. 存储类型指针,也就是指向类的元数据的指针,通过这个指针才能确定对象是属于哪个类的实例。

如果是数组的话,则还包含了数组的长度。

常见面试题_结果集_03


  • 对于加锁,ReentrantLock原理?和synchronized有什么区别?

相比于synchronized,ReentrantLock需要显式的获取锁和释放锁,相对现在基本都是用JDK7和JDK8的版本,ReentrantLock的效率和synchronized区别基本可以持平了。主要区别有以下几点:

  1. 等待可中断,当持有锁的线程长时间不释放锁的时候,等待中的线程可以选择放弃等待,转而处理其他的任务。
  2. 公平锁:synchronized和ReentrantLock默认都是非公平锁,但是ReentrantLock可以通过构造函数传参改变。只不过使用公平锁的话会导致性能急剧下降。
  3. 绑定多个条件:ReentrantLock可以同时绑定多个Condition条件对象。

ReentrantLock基于AQS(AbstractQueuedSynchronizer抽象队列同步器)实现。

AQS内部维护一个state状态位,尝试加锁的时候通过CAS(CompareAndSwap)修改值,如果成功设置为1,并且把当前线程id赋值,则表示加锁成功,一旦获取到锁,其他的线程将会被阻塞进入阻塞队列自旋,获得锁的线程释放锁的时候将会唤醒阻塞队列中的线程,释放锁的时候则会把state重新置为0,同时当前线程id置为空。


mysql

  • 语法
  • limit 索引从0开始
select distinct class_id from student
  • order by age asc从小到大,desc 从大到小
select name,case when (age > 60) then '老同学'
    when (age > 20 and age <= 60) then '年轻' else '小同学' end as age_level from student order by name asc
-- 获取当前日期
SELECT DATE() AS current_date;

-- 获取当前日期时间
SELECT DATETIME() AS current_datetime;

-- 获取当前时间
SELECT TIME() AS current_time;
-- 将姓名转换为大写
SELECT name, UPPER(name) AS upper_name
FROM employees;
-- 计算姓名长度
SELECT name, LENGTH(name) AS name_length
FROM employees;
-- 将姓名转换为小写并进行条件筛选
SELECT name, LOWER(name) AS lower_name
FROM employees;
-- 请在此处输入 SQL
select sum(score) as total_score,sum(score) / count(score) as avg_score,
 max(score) as max_score, min(score) as min_score from student
-- 请在此处输入 SQL
select class_id, sum(score) / count(score) as avg_score from student group by class_id
-- 请在此处输入 SQL
select class_id, exam_num, count(*) as total_num from student group by class_id, exam_num
-- 请在此处输入 SQL
HAVING 子句与条件查询 WHERE 子句的区别在于,WHERE 子句用于在 分组之前 进行过滤,而 HAVING 子句用于在 分组之后 进行过滤。
select class_id,sum(score) as total_score from student 
group by class_id having total_score > 150
-- 请在此处输入 SQL (cross join)
select  student_name, s.age student_age, s.class_id class_id,  class_name from student s,class c
select  student_name,s.age student_age,s.class_id, class_name,c.level class_level from student s join class c on s.class_id = 
-- 请在此处输入 SQL
select  student_name, s.age student_age, s.class_id, class_name,c.level class_level from student s
 left join class c on s.class_id = 
-- 请在此处输入 SQL
select name,score,class_id from student where class_id 
in (select distinct id from class)
-- 请在此处输入 SQL
select name,age,class_id from student where not exists
(select id from class where student.class_id = )
-- 请在此处输入 SQL
在 SQL 中,组合查询是一种将多个 SELECT 查询结果合并在一起的查询操作。

包括两种常见的组合查询操作:UNION 和 UNION ALL。

UNION 操作:它用于将两个或多个查询的结果集合并, 并去除重复的行 。即如果两个查询的结果有相同的行,则只保留一行。

UNION ALL 操作:它也用于将两个或多个查询的结果集合并, 但不去除重复的行 。即如果两个查询的结果有相同的行,则全部保留。
select name,age,score,class_id from student
union all
select name,age,score,class_id from student_new
-- 请在此处输入 SQL
在 SQL 中,开窗函数是一种强大的查询工具,它允许我们在查询中进行对分组数据进行计算、 同时保留原始行的详细信息 。

开窗函数可以与聚合函数(如 SUM、AVG、COUNT 等)结合使用,但与普通聚合函数不同,开窗函数不会导致结果集的行数减少。
SUM(计算字段名) OVER (PARTITION BY 分组字段名)

请你编写一个 SQL 查询,返回每个学生的详细信息(字段顺序和原始表的字段顺序一致),并计算每个班级的学生平均分(class_avg_score)。
select id,name,age,score,class_id,
avg(score) over (partition by class_id) as class_avg_score from student
-- 请在此处输入 SQL
本节教程我们将学习 sum over 函数的另一种用法:sum over order by,可以实现同组内数据的 累加求和 。

示例用法如下:

SUM(计算字段名) OVER (PARTITION BY 分组字段名 ORDER BY 排序字段 排序规则)

select id,name,age,score,class_id, 
sum(score) over (partition by class_id order by score asc) as class_sum_score from student
Rank 开窗函数是 SQL 中一种用于对查询结果集中的行进行 排名 的开窗函数。它可以根据指定的列或表达式对结果集中的行进行排序,并为每一行分配一个排名。在排名过程中,相同的值将被赋予相同的排名,而不同的值将被赋予不同的排名。

当存在并列(相同排序值)时,Rank 会跳过后续排名,并保留相同的排名。

Rank 开窗函数的常见用法是在查询结果中查找前几名(Top N)或排名最高的行。

Rank 开窗函数的语法如下:

RANK() OVER (
  PARTITION BY 列名1, 列名2, ... -- 可选,用于指定分组列
  ORDER BY 列名3 [ASC|DESC], 列名4 [ASC|DESC], ... -- 用于指定排序列及排序方式
) AS rank_column
其中,PARTITION BY 子句可选,用于指定分组列,将结果集按照指定列进行分组;ORDER BY 子句用于指定排序列及排序方式,决定了计算 Rank 时的排序规则。AS rank_column 用于指定生成的 Rank 排名列的别名。

-- 请在此处输入 SQL
select id,name,age,score,class_id,rank() over (partition by class_id order by score desc) as ranking from student
Row_Number 开窗函数是 SQL 中的一种用于为查询结果集中的每一行 分配唯一连续排名 的开窗函数。

它与之前讲到的 Rank 函数,Row_Number 函数为每一行都分配一个唯一的整数值,不管是否存在并列(相同排序值)的情况。每一行都有一个唯一的行号,从 1 开始连续递增。

Row_Number 开窗函数的语法如下(几乎和 Rank 函数一模一样):
ROW_NUMBER() OVER (
  PARTITION BY column1, column2, ... -- 可选,用于指定分组列
  ORDER BY column3 [ASC|DESC], column4 [ASC|DESC], ... -- 用于指定排序列及排序方式
) AS row_number_column

-- 请在此处输入 SQL
select id,name,age,score,class_id,row_number() over(partition by class_id order by score desc) as row_number from student
开窗函数 Lag 和 Lead 的作用是获取在当前行之前或之后的行的值,这两个函数通常在需要比较相邻行数据或进行时间序列分析时非常有用。

1)Lag 函数

Lag 函数用于获取 当前行之前 的某一列的值。它可以帮助我们查看上一行的数据。

Lag 函数的语法如下:

LAG(column_name, offset, default_value) OVER (PARTITION BY partition_column ORDER BY sort_column)
参数解释:

column_name:要获取值的列名。
offset:表示要向上偏移的行数。例如,offset为1表示获取上一行的值,offset为2表示获取上两行的值,以此类推。
default_value:可选参数,用于指定当没有前一行时的默认值。
PARTITION BY和ORDER BY子句可选,用于分组和排序数据。
2)Lead 函数

Lead 函数用于获取 当前行之后 的某一列的值。它可以帮助我们查看下一行的数据。

Lead 函数的语法如下:

LEAD(column_name, offset, default_value) OVER (PARTITION BY partition_column ORDER BY sort_column)
参数解释:

column_name:要获取值的列名。
offset:表示要向下偏移的行数。例如,offset为1表示获取下一行的值,offset为2表示获取下两行的值,以此类推。
default_value:可选参数,用于指定当没有后一行时的默认值。
PARTITION BY和ORDER BY子句可选,用于分组和排序数据。

-- 请在此处输入 SQL
select id,name,age,score,class_id,lag(name,1,null) over (partition by class_id order by score desc) as prev_name,
lead(name,1,null) over (partition by class_id order by score desc) as next_name  from student
-- 请在此处输入 SQL
select adventurer_id,adventurer_name,sum(reward_coins) as total_reward_coins from rewards
group by adventurer_id,adventurer_name order by total_reward_coins desc limit 3
-- 请在此处输入 SQL
select student_id,student_name,subject_id,subject_name,score,rank() over(partition by subject_id order by score desc) as score_rank from magic_scores