JAVA八股

几种创建线程的方法

1.继承thread类, 重写run方法,不是start方法
2.实现runnable接口,实现run方法,依然还是要用到thread,这种方式更常用。利用lamda表达式,直接通过匿名类的方式启动线程
3.callable,开启一个线程执行任务,可以拿到任务的结果。这个需要结合futureTask使用。callable是通过call方法实现线程。本质也是runnable
4.利用线程池创建线程。

创建线程池

1.fixedthreapol。创建的线程池选择的阻塞队列如果是无界的,可能会造成OOM。不建议,因为他们的参数是不可以设置的,灵活性不高。
2.singleThreadExecutor。不建议
建议使用ThreadPoolExecutor

线程池有哪几种状态

1.运行态,running 可以接受任务,正常处理任务
2.shutdown 不会接受任务,会处理队列中的剩余任务
3.stop 不接受任务,不处理任务
4.tidying ,当没有线程运行时会自动变成该状态,并且调用terminated(),该方法时空方法,留给程序员扩展
5.terminated,执行完terminated(),状态变为terminated

sychronized和reentrantlock的不同点

在这里插入图片描述
锁的信息主要指的是这把锁目前那个线程在使用。

threadlocal的应用场景,底层实现

1.提供线程本地存储机制,同一个线程内的数据传递,
2.每个线程内部有一个threadlocalmap的信息,去存储该线程需要缓存的值
3.内存泄漏问题,当线程使用完缓存的信息后不会自动挥手,只要线程不被回收,缓存的对象就不会被回收。需要手动清除对象。解决方法就是用完后就remove。
4.threadlocal经典场景就是连接管理(一个线程持有一个连接,该连接对象可以在不用的方法之间进行传递,线程之间不共享同一个连接)

reentrantlock的公平锁和非公平锁,底层是如何实现的

1.公平锁:先排队,排队获得资源
2.非公平锁,不会检查是否有线程排队,直接去竞争,没竞争到再去排队
无论什么锁当没有竞争到时都会去排队,并且这些锁都是可重入锁

sychronized的锁升级过程

1.偏向锁:在锁对象的对象头记录获得该锁的线程id,若该线程来获取锁便可直接获取,也就是支持锁重入
2.轻量级锁:当一个线程获得锁后,这便是偏向锁,若有第二个线程来竞争所,就会升级为轻量级锁,轻量级锁通过自旋实现,并不会阻塞线程
3.自旋次数过多,升级为重量级锁,会阻塞线程
4.自旋锁:当没有获取到锁时,会循环获取,利用cas的原理,一直去验证,不会消耗太多的操作系统资源,比较轻量

tomcat为什么自定义类加载器

tomcat会部署很多应用,每个应用是相互独立的,但应用间的类可能会名字相同,共用的类加载器不能实现类名称冲突,所以tomcat不同的应用自定义自己的类加载器,还可以方便实现热加载的功能

jdk jre jvm的区别

jdk是标准开发包,提供编译,运行的所有资源。jre是用于运行的java字节码文件
jvm是虚拟机,是jre一部分
jdk中包含了jre jre包含jvm

hashcode()与equals()的关系

在这里插入图片描述
重写equals就要重写hashcode

String StringBuffer StringBuilder的区别

String是常量,StringBuffer 线程安全,可变 StringBuilder线程不安全

泛型中extends和super的区别

extends表示子类,定义的时候不确定数据类型,super表示父类

== 和equals方法的区别

=== 如果是基本数据类型,比较值。如果是引用类型,比较引用地址
equals,具体看各个类的重写逻辑,没有重写就是比较引用地址

重载和重写的区别

重载方法名相同,参数列表不同
重载:发生在同一个类中,名相同,参数类型不同,个数不同,顺序不用,方法返回值和访问修饰符可以不同,编译时报错

list和set的区别

list有序可重复,允许多个null元素,可以用迭代器去除所有元素
set:无序,不可重复,只能用迭代器取元素

arraylist linkedlist的区别

数组和链表的区别

concurrentHashMap的扩容机制

原理:ConcurrentHashMap中主要采用的CAS+自旋,改成功就改,改不成功继续试(自旋)。也有synchronized配合使用。

cas流程很好理解,但在多cpu多线程的情况下会不会不安全,放心安全。java的cas其实是通过Unsafe类方法调用cpu的一条cas原子指令。操作系统本身是对内存操作做了线程安全的,篇幅太少也说不清楚,这里大家可以自行研究一下JMM,JMM不是本文重点。这里只要知道结论就是CAS可以保证原子性。不过它只提供单个变量的原子性,多个变量的原子性还需要借助synchronized。
Unsafe是java里的另类,java有个特点是安全,并不允许程序员直接操作内存,而Unsafe类可以,才有条件执行CAS方法。但是不建议大家使用Unsafe.class,因为不安全,sun公司有计划取消Unsafe。

ConcurrentHashMap源码解析,待定

hashmap发生了什么变化

1:1.7:数组+链表
18:数组+红黑树
2;1.7插入为头插法,1.8用尾插法,因为插入时也需要计算元素个数。
3: 1.8简化了哈希算法

hashmap的put方法

1.根据key通过哈希算法与与运算算出数组下标
2.判断数组下标是否为空,为空则放在那
3.不为空
1.7:判断是否需要扩容,扩容就先扩容,不需要就直接头插法
1.8:是否为红黑树,为红黑树则插入红黑树中
是否为链表,插入后看是否大于8,大于8则变为红黑树

深拷贝和浅拷贝

一个对象存在两种不同的属性,一种时基本数据类型,一种时实例对象
浅拷贝:只会拷贝基本数据类型的值,以及实习对象的引用地址
深拷贝:拷贝基本数据类型的值,会生成一个新的对象

copyOnWriteArraylist的底层原理

1.copyOnWriteArraylist内部用数组实现,在添加新元素时,会复制到一个新的数组中,写在新数组,读在老数组
2.写操作会加锁
3.写操作后把原数组指向新数组
4.允许写时读,适合读多写少,以内存换时间,不能保证实时性

什么是字节码?采用字节码的好处是什么

一次编译到处运行,可以跨平台,提高代码执行性能

java异常体系结构是怎样的

1.所有异常来自throwable
下面又包括错误和异常,错误会导致程序停止,异常可以进行捕捉然后特殊处理
异常分为运行时异常和非运行时异常,非运行时异常一般会编译不通过。运行时异常一般由程序的逻辑错误引起的。

异常处理中,什么时候抛出,什么时候捕获

异常相当于一种提示,抛出异常相当于通知上层的方法,当我们不能解决的异常就抛出异常。当我们可以处理这个异常,则处理异常。

java有哪些类加载器

JDK自带三个类加载器 bootstrap classloader ,extclassloader appclassloader
bootstrap classloader是extclassloader 的父类加载器,默认负责加载%JAVA_HOME%lib下的jar包和class文件
extclassloader是appclassloader的父类加载器,负责加载%JAVA_HOME%lib/ext下的jar包和class类
appclassloader是自定义类加载器的父类,负责加载classpath下的类文件

双亲委派模型

在这里插入图片描述
在这里插入图片描述
可以保证稳定性
在这里插入图片描述

JVM中哪些是线程共享区

在这里插入图片描述

项目如何排查jvm问题

在这里插入图片描述

一个对象从加载到jvm再到被gc清除,经历了什么过程

1.首先把字节码文件内容加载到方法区
2.然后根据类信息在堆区创建对象
3.对象会先分配在eden区,经过minor gc后,存活的进入suvivor区,然后每次拷贝年龄+1
4.当年龄超过15,则进入老年代
5.如果经过full gc,被标记为垃圾对象,就会被gc线程清理掉

怎么确定一个对象是不是垃圾

1.引用计数法,当引用个数为0则视为垃圾,无法解决循环引用
2.可达性算法,从跟对象往下找引用,找不到的对象就是垃圾

JVM有哪些垃圾回收算法

标记清除
复制算法,G1就是用的复制算法
标记-整理

jvm参数

JVM(Java Virtual Machine)参数是用于配置和控制Java虚拟机行为的命令行参数。这些参数可以帮助开发者优化JVM的性能、调整内存分配、启用或禁用特定功能等。以下是一些常见的JVM参数:

-Xms 和 -Xmx:

-Xms:设置JVM启动时分配的初始堆内存大小。
-Xmx:设置JVM可以使用的最大堆内存大小。
-Xss:

设置每个线程的栈大小。
-XX:PermSize 和 -XX:MaxPermSize(仅适用于Java 7及之前的版本):

-XX:PermSize:设置永久代的初始大小。
-XX:MaxPermSize:设置永久代的最大大小。
-XX:MetaspaceSize 和 -XX:MaxMetaspaceSize(适用于Java 8及之后的版本):

-XX:MetaspaceSize:设置元空间的初始大小。
-XX:MaxMetaspaceSize:设置元空间的最大大小。
-XX:+UseConcMarkSweepGC 和 -XX:+UseParallelGC 等:

选择使用的垃圾收集器。
-XX:+HeapDumpOnOutOfMemoryError:

当发生OutOfMemoryError时,将堆内存转储到一个文件中。
-XX:HeapDumpPath:

指定堆内存转储文件的路径。
-verbose:gc 和 -XX:+PrintGCDetails:

输出详细的垃圾收集日志。
-XX:+PrintTenuringDistribution:

输出每次GC后各个年龄段的对象数量。
-XX:SurvivorRatio:

设置新生代中Eden区与Survivor区的比例。
-XX:+UseAdaptiveSizePolicy:

启用或禁用自适应大小策略,该策略会自动调整新生代的大小和比例。
-XX:+DisableExplicitGC:

禁用System.gc()的调用。
-XX:+PrintCommandLineFlags:

打印JVM启动时使用的所有命令行标志。
这只是JVM参数的一小部分,JVM提供了大量的参数供开发者调整和优化Java应用程序的性能。在调整JVM参数时,建议首先了解每个参数的作用和潜在影响,并在适当的生产环境或测试环境中进行验证。

当使用G1垃圾回收器(Garbage-First Garbage Collector)时,可以设置以下一些参数来配置其行为:

-XX:+UseG1GC:
启用G1垃圾回收器。
-XX:G1HeapRegionSize:
设置G1堆区域的大小。G1将堆内存划分为多个大小相等的区域,这个参数决定了每个区域的大小。默认值是堆内存的1/2048,但通常建议设置为一个介于1MB到32MB之间的值。
-XX:MaxGCPauseMillis:
设置G1垃圾回收的目标暂停时间(毫秒)。G1会尝试在每次GC时满足这个暂停时间目标。较小的值会增加CPU的利用率以减少GC暂停,但也可能增加GC的频率。较大的值会允许较长的暂停时间,从而减少GC的频率,但可能增加单次GC的持续时间。
-XX:InitiatingHeapOccupancyPercent:
设置触发并发GC周期的堆占用百分比。当堆的使用率达到这个百分比时,G1会开始一个并发GC周期。默认值是45%,但可以根据应用程序的需求进行调整。
-XX:G1NewSizePercent 和 -XX:G1MaxNewSizePercent:
设置新生代的最小和最大百分比。这些参数决定了新生代在堆内存中所占的比例。
-XX:ParallelGCThreads:
设置并行GC线程的数量。这个值通常会自动设置,但也可以手动调整以优化性能。
-XX:ConcGCThreads:
设置并发GC线程的数量。这个值也通常会自动设置,但也可以手动调整。
-XX:G1ReservePercent:
设置保留堆内存的百分比,用于避免在GC过程中发生OutOfMemoryError。默认值是10%。
-XX:SurvivorRatio:
设置新生代中Eden区与Survivor区的比例。这个参数与G1不是直接相关,因为G1使用不同的区域划分方式,但如果你也关心新生代的行为,这个参数仍然可以调整。
-XX:InitiatingHeapOccupancyPercent 和 -XX:G1MixedGCCountTarget:
这两个参数共同控制混合GC的触发。InitiatingHeapOccupancyPercent 控制堆占用百分比,而 G1MixedGCCountTarget 控制多少次年轻代GC后触发一次混合GC。
-XX:+ExplicitGCInvokesConcurrent:
当调用 System.gc() 时,这个参数决定是否执行一个并发GC周期。默认是启用的。
-XX:+UseStringDeduplication:
启用字符串去重功能,可以减少堆内存中的字符串实例数量。
请注意,G1垃圾回收器有很多参数可以调整,但并不是所有参数都需要或都应该手动设置。通常,使用默认设置并根据应用程序的具体需求进行调整是一个好的开始。此外,在生产环境中使用G1之前,建议进行充分的测试和验证。

对线程安全的理解

多个线程同时执行,得到的结果是否正确

守护线程的理解

线程分为用户线程和守护线程,垃圾回收线程就是守护线程,他会一直运行,除非程序停止才会关闭

java死锁如何避免

造成死锁的原因
1一个线程每次只有一个线程使用
2.一个线程阻塞某个资源,不释放已占有资源
3.一个线程获得资源后,未使用完之前,不能被强行剥夺
4.若干线程形成循环等待

造成死锁必须达到这四个条件,打破死锁只要破坏其中一个条件就可以

开发过程中:
1.注意加锁顺序,保证每个线程按照同样的顺序加锁。例如都是先加a锁再加b锁
2.注意加锁时限,设置一个超时时间
3.注意死锁检查,保证异地时间可以解决

线程池的底层工作原理

队列+线程
1.先创建核心线程数个线程
2.当核心线程数都在工作,缓冲队列没有满,则放到队列中
3.线程数量大于核心线程数,缓冲队列满,没达到最大线程数,则开新的线程
4.当达到了最大线程数,则通过handler的指定策略来处理
5.当线程中的线程数量大于核心线程数,若某线程的空闲时间大于keeplivetime,线程将终止,这样可以实现动态的调整线程数
在Java中,线程池(ThreadPoolExecutor)的RejectedExecutionHandler接口定义了当线程池无法处理新任务时的策略。这个接口有一个方法void rejectedExecution(Runnable r, ThreadPoolExecutor executor),当线程池无法执行新任务时,就会调用这个方法。

ThreadPoolExecutor提供了几种内置的RejectedExecutionHandler策略,同时你也可以实现自己的策略。以下是内置的几种策略:

AbortPolicy:这是默认的拒绝策略。当新任务无法被处理时,直接抛出RejectedExecutionException。
CallerRunsPolicy:这个策略不会抛出异常,也不会丢弃任务,而是将任务回退到调用者线程中执行。这意味着,如果线程池已满,提交任务的线程将负责运行这个任务。
DiscardOldestPolicy:这个策略会丢弃线程池中最旧的任务,然后尝试重新提交新任务。这可能会导致某些任务永远不会被执行。
DiscardPolicy:这个策略会简单地丢弃无法处理的任务,不抛出任何异常。这也可能导致某些任务永远不会被执行。
如果你需要自定义拒绝策略,可以创建自己的类并实现RejectedExecutionHandler接口,然后传递给ThreadPoolExecutor的构造函数。

例如,以下是一个简单的自定义拒绝策略,当线程池已满时,它会记录一条错误日志:

java
public class CustomRejectedExecutionHandler implements RejectedExecutionHandler {
@Override
public void rejectedExecution(Runnable r, ThreadPoolExecutor executor) {
System.err.println("Task " + r.toString() + " was rejected from " + executor.toString());
}
}
然后,你可以在创建ThreadPoolExecutor时使用这个自定义的拒绝策略:

java
RejectedExecutionHandler handler = new CustomRejectedExecutionHandler();
ThreadPoolExecutor executor = new ThreadPoolExecutor(
corePoolSize,
maximumPoolSize,
keepAliveTime,
TimeUnit.MILLISECONDS,
workQueue,
threadFactory,
handler);
这样,当线程池无法处理新任务时,就会执行你的自定义拒绝策略。

ReentrantLock

都是使用AQS来进行排队,

ReentrantLock的tryLock()和lock()方法的区别

1.tryLock()尝试加锁,不会阻塞线程,有返回值
2.lock()表示阻塞加锁,无返回值

CountDownlatch和semaohore的底层原理是什么,有什么区别

CountDownLatch和Semaphore都是Java并发编程中常用的同步工具类,它们分别用于不同的场景,并有着各自不同的底层原理和实现方式。

CountDownLatch的底层原理
CountDownLatch是一个同步工具类,它允许一个或多个线程等待其他线程完成操作。CountDownLatch的底层原理主要是基于一个计数器,这个计数器在CountDownLatch实例创建时被初始化,并且只能通过countDown()方法递减。当计数器递减到0时,所有在这个CountDownLatch上等待的线程将被唤醒。

CountDownLatch的底层实现利用了AQS(AbstractQueuedSynchronizer)框架,这是一个用于构建锁和同步器的框架。在CountDownLatch中,AQS维护了一个状态(state),这个状态就是计数器的值。countDown()方法会尝试将状态减1,如果状态减1后变为0,那么会唤醒所有等待的线程。

Semaphore的底层原理
Semaphore也是一个同步工具类,它用于控制多个线程对共享资源的访问。Semaphore维护了一个计数器,这个计数器表示可用的资源数量。当线程需要访问共享资源时,它会尝试获取一个许可(通过acquire()方法),如果许可可用,则获取成功并继续执行;如果许可不可用,则线程将被阻塞,直到有许可可用。

Semaphore的底层实现同样利用了AQS框架。在Semaphore中,AQS维护了一个状态(state),这个状态表示可用的资源数量。acquire()方法会尝试获取一个许可,如果许可不可用,则当前线程将被加入到一个等待队列中,直到有许可可用。

区别
使用场景:CountDownLatch通常用于等待多个线程完成某个任务,而Semaphore则用于控制对共享资源的并发访问。
计数器含义:在CountDownLatch中,计数器表示需要等待的线程数量;而在Semaphore中,计数器表示可用的资源数量。
线程唤醒方式:当CountDownLatch的计数器减到0时,所有等待的线程都会被唤醒;而Semaphore在释放一个许可时,只会唤醒一个等待的线程。
公平性:Semaphore支持公平和非公平两种模式,而CountDownLatch则没有公平性的概念。
总的来说,CountDownLatch和Semaphore在底层原理上都是基于AQS框架实现的,但它们在使用场景、计数器含义、线程唤醒方式和公平性等方面有着明显的区别。

是的,它们都可以起到锁的作用,但它们实现锁的机制和使用场景有所不同。

Semaphore(信号量):这是一个计数器,可以用来限制对某个资源的并发访问数量。例如,如果有一个资源池包含10个资源,那么可以使用一个最大计数为10的Semaphore来确保同时只有10个线程可以访问这些资源。当一个线程使用完资源后,它会释放Semaphore中的一个许可,使得另一个等待的线程可以获取许可并访问资源。因此,Semaphore起到的是一个限流和防止资源过度访问的锁的作用。
CountDownLatch(倒计时锁存器):这通常用于等待一组线程完成它们的任务。例如,有一个任务需要多个线程共同完成,你可以使用CountDownLatch来确保所有线程都完成了它们的部分后,主线程才能继续执行。CountDownLatch包含一个计数器,这个计数器在开始时被设置为一个正数。每当一个线程完成了它的任务并调用countDown()方法时,计数器就会减1。当计数器减到0时,所有在CountDownLatch上等待的线程都会被唤醒。因此,CountDownLatch起到的是一个同步和等待所有线程完成任务的锁的作用。
总的来说,Semaphore和CountDownLatch都可以用来控制线程的行为和访问资源的权限,但它们实现的机制和使用场景有所不同。Semaphore主要用于限制并发访问,而CountDownLatch主要用于同步和等待一组线程完成任务。

AQS的理解,如何实现可重入锁

1.AQS是一个java线程同步的框架,是jdk中许多锁工具的核心实现框架
2.在aqs中,维护一个state和一个线程组成的双向链表队列,队列用来排队,state控制线程排队或者放行。
3.在可重入锁这个场景下,state就是用来表示加锁的次数。0标识无所,每加一次锁state+1,释放锁-1;

mybatis的优缺点

优点:
1.基于sql语句编程,比较灵活,sql写在XML里,解除sql与程序代码之间的耦合,便于统一管理;提供XML标签,支持编写动态SQL语句,并可重用
2.与JDBC相比,减少了50%以上的代码,消除大量的冗余代码
3.很好的与各种数据库兼容,因为是使用jdbc来连接数据库
4.能够与spring很好的集成
5.提供映射标签,支持对象与数据库的ORM字段关系映射;提供对象关系映射标签,支持对象关系组件维护

缺点:
1.sql的编写工作量大,尤其字段多,关联表多时
2.sql语句依赖于数据库,导致数据库的移植性差

#{} 和${}的区别是什么

#{}是预编译处理,是占位符,KaTeX parse error: Expected 'EOF', got '#' at position 15: {}是字符串替换,是拼接符 #̲{}处理时,会将其替换为?号,…{}时,会将其变为变量的值,调用Statement来赋值
使用#{}可以有效地防止sql注入,提高系统安全性

索引的基本原理

把无序的变成有序的数据

索引的设计原则

在这里插入图片描述

事务基本特性和隔离级别

在这里插入图片描述

什么是mvcc

多版本并发控制

在这里插入图片描述

什么是索引

mysql索引面试题 https://blog.csdn.net/yudiandemingzi/article/details/122329773

mysql日志面试题

https://zhuanlan.zhihu.com/p/410631152

mysql锁面试题

https://blog.csdn.net/yzx3105/article/details/126774223

mysql隔离级别面试题

https://blog.csdn.net/qq_40544893/article/details/123711413

redis面试题

https://blog.csdn.net/adminpd/article/details/122934938

扩容和收缩
当哈希表保存的键值对太多或者太少时,就要通过 rerehash(重新散列)来对哈希表进行相应的扩展或者收缩。具体步骤如下:

1)如果执行扩展操作,会基于原哈希表创建一个大小等于 ht[0].used * 2n 的哈希表(也就是每次扩展都是根据原哈希表已使用的空间扩大一倍创建另一个哈希表)。相反如果执行的是收缩操作,每次收缩是根据已使用空间缩小一倍创建一个新的哈希表。

2)重新利用上面的哈希算法,计算索引值,然后将键值对放到新的哈希表位置上。

3)所有键值对都迁徙完毕后,释放原哈希表的内存空间。

JAVA面经

https://blog.csdn.net/adminpd/article/details/122934938

Spring源码解析

在这里插入图片描述
在这里插入图片描述

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

  • 7
    点赞
  • 16
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值