面试准备

面试自己的一些准备

Spring常见面试总结

Synchronized和ReentrantLock的区别

Synchnorized类锁和对象锁的区别,对象锁的时候,就是交换的堆中的实例对象的markworld;

而对象锁的时候,在加载对象的时候会在堆中生成一个class对象的(java.lang.class,这个与方法取中的instanceKlass的java_mirror字段互指),对这个对象进行加锁。

cdg5DH

H0ieGt

牛客某个人的八股

JWT详解

手写单例模式

ThreadPoolExecutor

知识

字节二面问Java ThreadPool线程池?我来教你应对

ScreenShot2022-08-29at10.15.10 ScreenShot2022-08-29at10.16.03 ScreenShot2022-08-29at10.16.22

美团二面:聊聊线程池设计与原理,由表及里趣味解析

注意看:线程池监控里面,获取线程池大小,获取当前活跃工作线程数量等,都需要使用ReentrantLock来开锁实现。

![ScreenShot2022-08-29at10.12.32](https://gitee.com/JiaChengCC/u-pic-chart-bed/raw/master/uPic/Screen Shot 2022-08-29 at 10.12.32.png)

ThreadPoolExecutor源码实现中用的是ReentrantLock锁?

公平锁:并发环境中,每个线程在获取时会先查看此锁维护的队列,如果为空,或者当前线程是等待队列的第一个就占有锁,否则就会加入到队列中,以后会按照FIFO的规则从队列中取到自己。

非公平锁:上来就直接尝试占有锁,如果尝试失败,再采用类似公平锁的方式。

ReentrantLock而言,通过构造器指定该锁是否是公平锁,默认是非公平锁。非公平锁的优点在于吞吐量比公平锁大。

Synchronized和ReentrantLock的区别

![qubie](https://gitee.com/JiaChengCC/u-pic-chart-bed/raw/master/uPic/Screen Shot 2022-08-29 at 10.08.18.png)

synchronize锁升级

锁的四种状态及升级过程

线程A在进入同步代码块前,①先检查MarkWord中的线程ID是否与当前线程ID一致,如果一致(还是线程A获取锁对象),则无需使用CAS来加锁、解锁。

如果不一致,②再检查是否为偏向锁,如果不是,则自旋等待锁释放。

如果是,③再检查该线程是否存在(偏向锁不会主动释放锁),如果不在,则CAS设置线程ID为线程A的ID,此时依然是偏向锁。

如果还在,④则暂停该线程,同时将锁标志位设置为00即轻量级锁(将MarkWord复制到该线程的栈帧中并将MarkWord设置为栈帧中锁记录)。线程A自旋等待锁释放。

⑤如果自旋次数到了该线程还没有释放锁,或者该线程还在执行,线程A还在自旋等待,这时又有一个线程B过来竞争这个锁对象,那么这个时候轻量级锁就会膨胀为重量级锁。重量级锁把除了拥有锁的线程都阻塞,防止CPU空转。

⑥如果该线程释放锁,则会唤醒所有阻塞线程,重新竞争锁。

手写有关参数的

Redis内存回收

内存过期策略 + 内存淘汰策略

Redis过期策略

周期删除

惰性删除:用的时候看下是否删除

redis中两者相结合

Redis淘汰策略

彻底弄懂Redis的内存淘汰策略

默认:noeviction,不淘汰任何key,但是内存满时不允许写入新的数据。

IMG_2E888F9DFDEE-1

IO多路复用

![ScreenShot2022-08-29at15.09.54](https://gitee.com/JiaChengCC/u-pic-chart-bed/raw/master/uPic/Screen Shot 2022-08-29 at 15.09.54.png)

![ScreenShot2022-08-29at15.11.01](https://gitee.com/JiaChengCC/u-pic-chart-bed/raw/master/uPic/Screen Shot 2022-08-29 at 15.11.01.png)

IMG_708C58625183

运行时非运行时异常

KaHVP3

下面这一题貌似选C,其他的都是对的。

1331662474023_.pic

c2jCx6

HashMap为什么线程不安全

JDK1.7 HashMap线程不安全体现在:死循环、数据丢失
JDK1.8 HashMap线程不安全体现在:数据覆盖

类型擦除与解决方案

Java通过反射获取泛型类型信息

在Java中可以通过反射获取泛型信息的场景有如下三个:

  • (1)成员变量的泛型
  • (2)方法参数的泛型
  • (3)方法返回值的泛型

在Java中不可以通过反射获取泛型信息的场景有如下两个:

  • (1)类、接、泛型方法 声明的泛型。(只要是声明处的泛型,那就都获取不了,但应该可以通过字节码技术进行获取,因为这些泛型信息应该还是被保存在了class文件中没有丢失的)
  • (2)局部变量的泛型,是因为反射连局部变量都访问不了,就更不要提访问它的泛型了,非要访问的话必须通过字节码技术。

Java中的泛型会被类型擦除,那为什么在运行期仍然可以使用反射获取到具体的泛型类型?

运行期只能获取当前class文件中包含泛型信息的泛型类型

而不能在运行时动态获取某个泛型引用的类型

可以通过反射获取到的泛型信息一定是某个class作为成员变量、方法返回值等位置的具体泛型类型,比如:

1
2
3
4
5
public class Test<T> {
private Test<Integer> test;

private T item;
}

可以通过反射获取成员test的Integer泛型信息,但是无法获取item的实际类型。

RabbitMQ和Kafka比较

RabbitMQ和Kafka比较

优先选择RabbitMQ的条件:

·高级灵活的路由规则
·消息时序控制(控制消息过期或消息延迟)
·高级的容错处理能力,在消费者更有可能处理消息不成功的情景中(瞬时或持久)
·更简单的消费者实现

优先选择Kafka的条件:

·吞吐量更高
·严格的消息顺序
·延长消息留存时间,包括过去消息重放的可能,消息回溯
·传统解决方案无法满足的高伸缩能力

Comparable和Comparator两个接口的区别

![image-20220912192322174](/Users/dinosaur/Library/Application Support/typora-user-images/image-20220912192322174.png)

Spring框架中的单例Bean是线程安全的吗?

答案:不是

Spring默认的bean是单例的,也没有进行封装处理,所以不是线程安全的。

但是,共享不一定会有线程安全问题。
如果某个bean我们定义了共享数据,且可以对共享数据进行修改,这样才会造成线程安全问题。比如我们在线程中定义一个count++,那么这个数就是不安全的,每次线程到来都会被执行一次,count值加一。
controller、service、dao本身不是安全的,但是,我们只在这些方法中相互调用,那么不会产生线程安全问题。
Dao层会操作数据库,但是每一个和数据库连接的connection,都会被数据库的事务机制管理,如果开启了Spring事务机制,那么也会被其管理。这都不会造成线程安全问题。
如果,我们必须要在bean中定义有状态的实例或变量,那么我们可以有以下几种办法,保证安全。

1 使用ThreadLocal,把变量变成线程私有的
2 synchronized、lock、CAS,保证互斥访问临界区
3 把bean从单例(singleton)设置成原型(protopyte)

ThreadLocal使用不当造成内存溢出

ThreadLocal,底层使用map,当前线程的对象作为key,value则是你想让变成私有属性的变量,或实例对象。那么当该线程要使用这个变量或实例时候,就可以从map中get出来。
每一个线程到来后,都会去创建map,如果是线程池中的核心线程,这个线程不死亡,它每次执行到这里都会往map中存对象,可能导致内存溢出。
如何解决呢?线程每次get(key)使用过后,就把map中的数据删除掉,remove(key)。

多线程run()中如何捕获运行时异常?

在实现Runnable或者继承Thread类实现多线程的时候,都要实现或者重写run()方法,查看源码可以看到run()是不允许抛出异常的,那么如果在run()方法中产生了运行时异常如何捕获呢?

Thread类中有UncaughtExceptionHandler这个接口来实现对异常的捕获,我们只需将其方法重写,创建这个实现类的实例O,在线程start之前,使用thread.setUncaughtExceptionHandler(O)将这个实例传入此方法。下面直接看例子。如果在将第四行代码注释掉后可以发现直接抛出异常,无法捕获异常,即使你在主函数中用try,catch捕获也是会直接抛出异常的。那么将第四行注释取消后,运行结果如下,可以看到已经捕获异常。

java正确关流_JAVA中如何优雅的关闭各种流

java优雅关闭io流

快速失败(fail-fast)以及其中的问题与安全失败(fail-safe)

Fail-fast

单线程

当在用迭代器(Iterator)或者增强for循环(增强for循环的底层也是迭代器)对一个集合进行遍历操作时,如果遍历的过程中集合的结构发生了变化,就会抛出并发修改异常ConcurrentModificationException。

要想避免抛出异常,应该使用Iterator对象的remove()方法。

多线程

当一个线程在对一个集合进行遍历操作的时候,如果其他线程对这个集合的结构进行了修改,就会抛出并发修改异常ConcurrentModificationException。

fail-safe

Java在java.util.concurrent包中为我们提供了安全失败的集合类,如 ConcurrentHashMap、CopyOnWriteArrayList等。其原理在于开始遍历之前会将原集合完整的复制一份,前者在复制的集合上进行遍历,在原集合上进行元素添加、删除操作,后者反之,这样就可以避免了ConcurrentModificationException异常。

当然,这类集合也有一些缺点:iterator不能保证返回集合更新后的数据,因为其工作在集合克隆上,而非集合本身。其次,创建集合拷贝需要相应的开销,包括时间和内存。

java中的hashCode计算方法总结

string的属性 value 就是字符数组,计算方法,s[0]*31^(n-1) + s[1]*31^(n-2) + … + s[n-1]

1
2
3
4
5
6
7
8
9
10
11
12
public int hashCode() {
int h = hash;
if (h == 0 && value.length > 0) {
char val[] = value;

for (int i = 0; i < value.length; i++) {
h = 31 * h + val[i];
}
hash = h;
}
return h;
}

为什么重写equals方法,还必须要重写hashcode方法

1.为了提高效率

2.为了保证同一个对象

a 保证在equals相同的情况下hashcode值必定相同,如果重写了equals而未重写hashcode方法,可能就会出现两个没有关系的对象equals相同的(因为equal都是根据对象的特征进行重写的),但hashcode确实不相同的。

b hash类存储结构(HashSet、HashMap等等)添加元素会有重复性校验,校验的方式就是先取hashCode判断是否相等(找到对应的位置,该位置可能存在多个元素),然后再取equals方法比较(极大缩小比较范围,高效判断),最终判定该存储结构中是否有重复元素。

Java中抽象类和接口的介绍及二者间的区别

抽象类的特点

1、抽象类不能被实例化,即不能使用new关键字来实例化对象,只能被继承;

2、包含抽象方法的一定是抽象类,但是抽象类不一定含有抽象方法;

3、抽象类中的抽象方法的修饰符只能为public或者protected,默认为public;

4、抽象类中的抽象方法只有方法体,没有具体实现;

5、如果一个子类实现了父类(抽象类)的所有抽象方法,那么该子类可以不必是抽象类,否则就是抽象类;

6、抽象类可以包含属性、方法、构造方法,但是构造方法不能用于实例化,主要用途是被子类调用。

接口

1、接口可以包含变量、方法;变量被隐士指定为public static final,方法被隐士指定为public abstract(JDK1.8之前);

2、接口支持多继承,即一个接口可以extends多个接口,间接的解决了Java中类的单继承问题;

3、一个类可以实现多个接口;

4、JDK1.8中对接口增加了新的特性:

​ 4.1、默认方法(default method):JDK 1.8允许给接口添加非抽象的方法实现,但必须使用default关键字修饰;定义了default的方法可以不被实现子类所实现,但只能被实现子类的对象调用;如果子类实现了多个接口,并且这些接口包含一样的默认方法,则子类必须重写默认方法;

​ 4.2、静态方法(static method):JDK 1.8中允许使用static关键字修饰一个方法,并提供实现,称为接口静态方法。接口静态方法只能通过接口调用(接口名.静态方法名)。

数据量很大的排序问题 大量数据如何排序

问题一:若有1T的数据,比如 只有两列,身份证号和姓名 需要实现由大到小排序,你用什么办法,能否做到 复杂度为O(n),说说你的思路和想法?

问题二:有10个G的数据,也是一样,比如两列,身份证号和姓名,如果两条数据一样,则表示该两条数据重复了,现在给你512的内存,把这10G中重复次数最高的10条数据取出来。

我的思路是:这么大的数据,用普通的排序一定不行,

可以这样,用身份证号的前三位切割这个数据,这样会分成999份,

每一份再进行排序,比如构造一个平衡二叉树,最典型的的就是TreeMap和TreeSet(TreeSet底层是使用了TreeMap算法,而TreeMap算法底层是实现了红黑树的平衡二叉树的排序);

然后按照文件名进行排序,这样就实现了大数据排序;

因为排序二叉树的复杂度为O(lgn)到O(n) ;

因此我们可以做到 O(n)

问题二:

解法是一样的 按照身份证号前三位 分割999份,然后对这每个文件找到重复的最多的十条,这样,我们得到了999个文件,每个文件有 10条数据

在对这个999*10条进行排序找到 重复率最高的十条即可;

大数据的文件归并排序

第一 将数据分成若干份(我们这里以10份为例子),每份都接近内存的最大数据量,这样子,将文件上的数据读入内存中,将它排序,结束后我们就可以得到10份有序的文件数据了。

第二步,归并文件,将两个文件归并成一个有序的文件后,在让这个有序的文件与第三个文件归并,这样依次下来,我们就可以得到一个有序的文件了。

Author: Jcwang

Permalink: http://example.com/2022/08/29/%E5%85%AB%E8%82%A1%E9%9D%A2%E8%AF%95%E5%87%86%E5%A4%87/