java-后端八股文

文章目录

  • java 基础
    • 创建对象的5种方式
    • 多态
    • throwable
    • Collection
    • Queue
    • HasMmap
    • CurrentHashMap
    • java线程同步机制
    • 接口和抽象类
    • hashcode equals ==
    • ArrayList LinkedList
    • Synchronized使用
    • Happen-Before原则
  • Java-Spring
    • spring的aop实现的两种方式
    • spring的循环依赖如何解决
    • Spring-@Transaction生效
    • Spring事务传播行为
  • 并发编程
      • volatile实现原理
  • Java-线程
    • 线程的状态
    • java线程池的数量依据
    • java创建线程池的方式
    • 线程池拒接策略
    • ThreadPoolExecutor的成员变量
    • 线程运行
  • Java-锁
    • AQS
    • lock和synchronize的区别
    • lock如何保证执行过程的可见性和有序性
    • 互斥锁、自旋锁、条件锁、读写锁、可重入锁、偏向锁、公平锁、非公平锁、可重入锁、共享锁、排它锁、轻量级锁、重量级锁
  • java-jvm
    • Synchronized 和 volatile 如何保证可见性和原子性
    • CAS 是怎么保证原子性的
    • new出来的对象是一创建出来就进入堆中的吗
    • TLAB
    • jvm-如何判断对象需要回收
    • jvm-垃圾回收算法
    • jvm-典型的垃圾收集器
    • Jvm内存溢出排查
  • 操作系统
    • 进程与线程的区别
    • 进程上下文切换比线程上下文切换
    • 纤程
    • 线程同步进制
    • 死锁产生的四个条件
    • 有了互斥锁,为什么还要条件锁
    • select、poll、epoll
  • 计算机 络
    • TCP 三次握手 四次挥手
    • 为什么连接的时候是三次握手,关闭的时候却是四次握手
    • 为什么TIME_WAIT状态需要经过2MSL(最大 文段生存时间)才能返回到CLOSE状态
    • 如果已经建立了连接,但是客户端突然出现故障了怎么办
  • 数据结构
  • 中间件
    • MQ
      • 工作模式
    • Kafka
      • Kafka的架构
      • kafka 高性能的原因
      • ack机制
    • redis
      • redis多并发竞争key
      • redis性能好的原因
      • redis 定期删除
      • redis内存的淘汰机制
  • 数据库
    • 三范式
      • 三范式
    • Mysql
      • Mysql 怎么实现可重复读
      • Mysql 怎么实现事务的
      • mysql日志种类
      • MySQL中binlog和redo log的一致性问题
      • Mysql-索引分类
      • mysql-解决幻读
  • Linux
    • linux下查看进程下的线程信息
    • linux权限命令

java 基础

创建对象的5种方式

  1. new
  2. Class 对象的newInstance()方法
  3. 类构造器,Demo.class.getConstructor.newInstance()
  4. 反序列化
  5. Ojbect.clone()方法

多态

分类

  1. 编译期间:方法重载,静态分派
  2. JVM运行期间:方法重写,动态分派

静态和变量看左边(父类),方法行为看右边(子类)
javap -c demo.class 命令输出代码的字节码

throwable

Queue

java线程池的数量依据

  1. 如果是CPU密集型应用,则线程池大小设置为N+1(或者是N),线程的应用场景:主要是复杂算法
  2. 如果是IO密集型应用,则线程池大小设置为2N+1(或者是2N),线程的应用场景:主要是:数据库数据的交互,文件上传下载, 络数据传输等等
    +1的原因是:即使当计算密集型的线程偶尔由于缺失故障或者其他原因而暂停时,这个额外的线程也能确保CPU的时钟周期不会被浪费
  3. 在多核多线程池情况下
    3.1 最佳线程数目 = ((线程等待时间+线程CPU时间)/线程CPU时间 )* CPU数目

java创建线程池的方式

  1. 通过ThreadPoolExecutor创建

线程池拒接策略

  1. AbortPolicy:丢弃任务并抛出 RejectedExecutionException 异常
  2. CallerRunsPolicy:将直接在 正在被调用的线程 的 execute() 方法中 运行被拒绝的task
  3. DiscardPolicy:丢弃任务,但是不抛出异常
  4. DiscardOldestPolicy:丢弃队列最早的未处理任务,然后重新尝试执行任务

ThreadPoolExecutor的成员变量

  1. corePoolSize核心池大小、maximumPoolSize最大线程数量,它们是用来控制线程的数目,总体流程为1:若小于Core则新建线程执行;2:若大于等于Core,则加入BlockingQueue;3:若BlockingQueue满了之后,若小于Maximum,则新建线程来执行任务,若大于等于Maximum,则执行拒绝策略
  2. threadFactory线程工厂,它是用来创建线程的地方,Executors中默认的线程工厂会将守护线程属性设置为false,线程的优先级设置为Normal登记;Executors还提供了权限线程工厂privilegedThreadFactory,可以对其的访问权限进行控制
  3. BlockingQueue workQueue任务队列,用来存放需要等待执行的任务,根据策略不同,Executors中提供了4种阻塞队列,不含容量的同步队列Synchronous队列,它不保存任务,一直创建新的线程执行,直到到maximum,然后执行拒绝策略;LinkedBlockingQueue无界队列,所以可以一直添加直到系统OOM;ArrayBlockingQueue它是有界的;PriorityBlockingQueue根据优先级的大小进行执行任务,Array和Linked则根据FIFO来执行任务
  4. keepAliveTime存活时间,对于超过CorePoolSize大小的线程最多存活的时间;
  5. rejectedExecutionHandler拒绝策略,对于超标,不能执行的拒绝策略;

线程运行

  1. 线程同步运行 CountDownLatch实现
  2. 线程顺序执行 volatile和CountDownLatch实现
  3. 线程交替执行 ReentrantLock以及Condition来实现

Java-锁

AQS

lock和synchronize的区别

java-后端八股文
  1. Lock是一个接口,而synchronized是关键字。
  2. synchronized会自动释放锁,而Lock必须手动释放锁。
  3. Lock可以让等待锁的线程响应中断,而synchronized不会,线程会一直等待下去。
  4. 通过Lock可以知道线程有没有拿到锁,而synchronized不能。
  5. Lock能提高多个线程读操作的效率。
  6. synchronized能锁住类、方法和代码块,而Lock是块范围内的

lock如何保证执行过程的可见性和有序性

互斥锁、自旋锁、条件锁、读写锁、可重入锁、偏向锁、公平锁、非公平锁、可重入锁、共享锁、排它锁、轻量级锁、重量级锁

  1. 互斥锁
    共享资源的使用是互斥的,即一个线程获得资源的使用权后就会将该资源加锁,使用完后会将其解锁,如果在使用过程中有其他线程想要获取该资源的锁,那么它就会被阻塞陷入睡眠状态,直到该资源被解锁才会被唤醒,如果被阻塞的线程不止一个,那么它们都会被唤醒,但是获得资源使用权的只有一个线程,其它线程又陷入沉睡.
  2. 自旋锁
    自旋锁是一种特殊的互斥锁,当资源被枷锁后,其他线程想要再次加锁,此时该线程不会被阻塞睡眠而是陷入循环等待状态(不能在做其它事情),循环检查资源持有者是否已经释放了资源,这样做的好处是减少了线程从睡眠到唤醒的资源消耗,但会一直占用CPU的资源。适用于资源的锁被持有的时间短,而又不希望在线程的唤醒上花费太多资源的情况。
  3. 条件锁
    条件变量用来自动阻塞一个线程,直到某特殊情况发生,通常条件变量和互斥锁同时使用。
    条件变量使我们可以睡眠等待某条件出现。
    条件变量是利用线程间共享的全局变量进行同步的一种机制,主要包括两个动作:
    1)一个线程因 “条件不成立” 而挂起等待
    2)另一个线程使 “条件成立”, 并发出信
  4. 读写锁
    它拥有读状态加锁、写状态加锁、不加锁这三种状态。
    只有一个线程可以占有写状态的锁,但可以有多个线程同时占有读状态锁,这也是它可以实现高并发的原因。当其处于写状态锁下,任何想要尝试获得锁的线程都会被阻塞,直到写状态锁被释放;如果是处于读状态锁下,允许其它线程获得它的读状态锁,但是不允许获得它的写状态锁,直到所有线程的读状态锁被释放;为了避免想要尝试写操作的线程一直得不到写状态锁,当读写锁感知到有线程想要获得写状态锁时,便会阻塞其后所有想要获得读状态锁的线程。所以读写锁非常适合资源的读操作远多于写操作的情况
  5. 可重入锁
    同一个线程可以多次获得该资源锁,别的线程必须等该线程释放所有次数的锁才可以获得
  6. 偏向锁
    大多数情况下,锁不仅不存在多线程竞争,而且总是由同一个线程多次获得,为了让线程获得锁的代价更低而引入了偏向锁。JDK1.6之后 偏向锁默认开启
    偏向锁是锁状态中最乐观的一种锁:从始至终只有一个线程请求同一把锁

java-jvm

Synchronized 和 volatile 如何保证可见性和原子性

  1. 可见性
    1.1. Synchronized
    1.1.1 线程加锁时,将清空工作内存中共享变量的值,从而使用共享变量时需要从主内存中重新读取最新的值
    1.1.2 线程解锁前,必须把共享变量的最新值刷新到主内存中
    1.1.3 1)获得互斥锁2)清空工作内存 3)从主内存拷贝变量的最新副本到工存 4)执行代码 5)将更改后的共享变量的值刷新到主内存 6)释放互斥锁
    1.2 Volatile 通过内存屏障和指令重排序来实现
    1.2.1 对volatile变量执行写操作时,会在写操作后加入一条store屏障指令。
    1.2.2 对volatile变量执行读操作时,会在读操作后加入一条load屏障指令
  2. 原子性
    2.1 Synchronized 支持
    2.2 Volatile 不支持
  3. Synchronized 和 Volatile 区别
    3.1 volatile比synchronized更轻量,不需要加锁,不会阻塞线程
    3.2 synchronized既能保证可见性,又能保证原子性,而volatile只能保证可见性,无法保证原子性,但java的内存模型保证声明为volatile的long和double变量的get和set操作是原子的

CAS 是怎么保证原子性的

  1. 当虚拟机遇到一条new指令时候,首先去检查这个指令的参数是否能在常量池中能否定位到一个类的符 引用,并且检查这个符 引用代表的类是否已被加载、解析和初始化过。如果没有,那必须先执行相应的类加载过程。
  2. 在类加载检查通过后,接下来虚拟机将为新生的对象分配内存。对象所需的内存的大小在类加载完成后便可以完全确定,为对象分配空间的任务等同于把一块确定大小的内存从Java堆中划分出来
    2.1 指针碰撞(Bump the Pointer):假设Java堆的内存是绝对规整的,所有用过的内存都放一边,空闲的内存放在另一边,中间放着一个指针作为分界点的指示器,那所分配内存就仅仅把那个指针向空闲空间那边挪动一段与对象大小相等的距离
    2.2 空闲列表(Free List):如果Java堆中的内存并不是规整的,已使用的内存和空间的内存是相互交错的,虚拟机必须维护一个空闲列表,记录上哪些内存块是可用的,在分配时候从列表中找到一块足够大的空间划分给对象使用。
  3. 在并发情况下划分不一定是线程安全的,有可能出现正在给A对象分配内存,指针还没有来得及修改,对象B又同时使用了原来的指针分配内存的情况,解决这个问题两种方案:
    3.1 分配内存空间的动作进行同步处理:实际上虚拟机采用CAS配上失败重试的方式保证了更新操作的原子性
    3.2 内存分配的动作按照线程划分在不同的空间中进行:为每个线程在Java堆中预先分配一小块内存,称为本地线程分配缓冲(Thread Local Allocation Buffer, TLAB)
  4. 内存分配完成后,进行初始化

new出来的对象是一创建出来就进入堆中的吗

逃逸分析(Escape Analysis) – 分析对象的动态作用域。假如我们在一个方法内定义了一个对象,如果它被作为参数传递到其他地方,被本方法外的方法引用,这就就叫做方法逃逸

如果我们经过逃逸分析发现,某个对象并没有发生方法逃逸,那么它的生命周期则始于方法调用,卒于方法结束,那么此时它就是方法内的局部变量,而堆内存是线程间共享的,如果将它分配到堆中,方法结束后,它将不在被任何对象所引用,还需要GC进行回收,很不划算,于是 JIT就会将其分配到方法的栈帧中,这就是栈上分配。实际上在HotSpot中,栈上分配并不是直接在方法的栈帧中放入一个对象,它是通过标量替换的方式存储的,即将对象分解成组成对象的若干个成员变量,这些变量是无法再分解的更小的数据,叫做标量,然后用这些标量来代替之前的对象,这就叫标量替换。通过标量替换,原本的一个对象,被替换成多个成员变量。而原本需要在堆上分配的内存,也就不再需要了,完全可以在本地方法栈中完成对成员变量的内存分配。

TLAB

TLAB (Thread Local Allocation Buffer,线程本地分配缓冲区)是 Java 中内存分配的一个概念,它是在 Java 堆中划分出来的针对每个线程的内存区域,专门在该区域为该线程创建的对象分配内存。它的主要目的是在多线程并发环境下需要进行内存分配的时候,减少线程之间对于内存分配区域的竞争,加速内存分配的速度。TLAB 本质上还是在 Java 堆中的,因此在 TLAB 区域的对象,也可以被其他线程访问

如果没有启用 TLAB,多个并发执行的线程需要创建对象、申请分配内存的时候,有可能在 Java 堆的同一个位置申请,这时就需要对拟分配的内存区域进行加锁或者采用 CAS 等操作,保证这个区域只能分配给一个线程

启用了 TLAB 之后(-XX:+UseTLAB, 默认是开启的),JVM 会针对每一个线程在 Java 堆中预留一个内存区域,在预留这个动作发生的时候,需要进行加锁或者采用 CAS 等操作进行保护,避免多个线程预留同一个区域。一旦某个区域确定划分给某个线程,之后该线程需要分配内存的时候,会优先在这片区域中申请。这个区域针对分配内存这个动作而言是该线程私有的,因此在分配的时候不用进行加锁等保护性的操作

jvm-如何判断对象需要回收

  1. 引用计数法
    缺点 循环引用 造成内存泄露
  2. 可达性分析
    可作为root的
    静态属性(被static修饰的属性)
    常量(被static final修饰的属性)
    虚拟机栈(本地变量表)中引用的对象
    本地方法栈中引用的对象

jvm-垃圾回收算法

  1. 标记-清除
  2. 标记-复制
  3. 标记-整理
  4. 分代回收

jvm-典型的垃圾收集器

1.Serial/Serial Old收集器 是最基本最古老的收集器,它是一个单线程收集器,并且在它进行垃圾收集时,必须暂停所有用户线程。Serial收集器是针对新生代的收集器,采用的是Copying算法,Serial Old收集器是针对老年代的收集器,采用的是Mark-Compact算法。它的优点是实现简单高效,但是缺点是会给用户带来停顿。
2.ParNew收集器 是Serial收集器的多线程版本,使用多个线程进行垃圾收集。
3.Parallel Scavenge收集器 是一个新生代的多线程收集器(并行收集器),它在回收期间不需要暂停其他用户线程,其采用的是Copying算法,该收集器与前两个收集器有所不同,它主要是为了达到一个可控的吞吐量。
4.Parallel Old收集器 是Parallel Scavenge收集器的老年代版本(并行收集器),使用多线程和Mark-Compact算法。
5.CMS(Concurrent Mark Sweep)收集器是一种以获取最短回收停顿时间为目标的收集器。CMS 收集器仅作用于老年代的收集。
6.G1收集器 是当今收集器技术发展最前沿的成果,它是一款面向服务端应用的收集器,它能充分利用多CPU、多核环境。因此它是一款并行与并发收集器,并且它能建立可预测的停顿时间模型

Jvm内存溢出排查

  1. jmap -heap pid
  2. 下载MemoryAnalyzer MAT工具导入第一步导出的heap.bin进行分析

操作系统

进程与线程的区别

(1)进程有自己的独立地址空间,线程没有
(2)进程是资源分配的最小单位,线程是CPU调度的最小单位
(3)进程和线程通信方式不同,同一进程下的线程共享数据(比如全局变量,静态变量)
(4)进程上下文切换开销大,线程开销小
(5)一个进程挂掉了不会影响其他进程,而线程挂掉了会影响其他线程

进程上下文切换比线程上下文切换

进程切换分两步:
1.切换页目录以使用新的地址空间
2.切换内核栈和硬件上下文
对于linux来说,线程和进程的最大区别就在于地址空间,对于线程切换,第1步是不需要做的,第2是进程和线程切换都要做的。
切换的性能消耗:
1、线程上下文切换和进程上下问切换一个最主要的区别是线程的切换虚拟内存空间依然是相同的,但是进程切换是不同的。这两种上下文切换的处理都是通过操作系统内核来完成的。内核的这种切换过程伴随的最显著的性能损耗是将寄存器中的内容切换出。
2、另外一个隐藏的损耗是上下文的切换会扰乱处理器的缓存机制。简单的说,一旦去切换上下文,处理器中所有已经缓存的内存地址一瞬间都作废了。还有一个显著的区别是当你改变虚拟内存空间的时候,处理的页表缓冲(processor’s Translation Lookaside Buffer (TLB))被全部刷新,这将导致内存的访问在一段时间内相当的低效。但是在线程的切换中,不会出现这个问题。

纤程

  1. 是用户态的线程,是线程中的线程,切换

    声明:本站部分文章及图片源自用户投稿,如本站任何资料有侵权请您尽早请联系jinwei@zod.com.cn进行处理,非常感谢!

上一篇 2021年8月22日
下一篇 2021年8月22日

相关推荐