随机小知识

关于线程:

多线程是什么?

同一时间内有多条线程同时执行任务,多线程有助于提高并发效率、系统性能。

关于并发、并行、串行、高并发

  • 并发:同一时间段,有两条及以上线程交替执行,相互争夺系统资源的场景。
  • 并行:在同一时间点,同时执行多条任务,这要求CPU不可以是单核。
  • 串行:线程间按照顺序一个接着一个的执行。
  • 高并发:发并发是一种概念,是针对系统而言的,指的是请求达到甚至超过了系统的承受范围上限。

什么是Java多线程同步

在多线程环境下,通过合适的机制、技术手段协调线程的执行,保证线程对资源的访问是原子、有序、其他线程可见的,例如可以通过synchronized加锁、atomic原子类、volatile关键字、等待唤醒机制wait()、notify()等。

Java中线程间的通信方式

  1. Object类中提供了通信的方法wait、notify、notifyAll
  2. 同步锁synchronized,保证线程操作的有序性
  3. volatile修饰变量,保证操作的有序性和其他线程可见性
  4. J.U.C中的包实现线程通信

为什么wait, notify和notifyAll这些方法不在thread类里面?

因为wait、notify、notifyAll这些方法是Java多线程中实现同步与通信的机制,与特定的线程实例是无关的,另外这些方法要在同步方法或者同步代码块中调用,由持有对象监视器锁的线程来调用,如果在Thread中就只可以通过Thread对象来调用,那么粒度太小了,相当于没锁

如何调用wait方法的,是通过 if 还是 while?

只有获取到锁对象的线程才可以调用wait方法,否则会报错,作用是当前线程放弃当前所持有的锁,进入等待状态,要用while执行,原因是在if语句中调用wait,唤醒后不会进行判断,有可能当前仍然是不具备执行条件的。

如何停止一个正在执行的线程

  • stop,不推荐使用,强制停止,过期方法
  • interrupt,优雅的去通知线程关闭

有三个线程T1,T2,T3,如何保证顺序执行?

  • 可以使用join,让线程进入等待状态按顺序进行执行任务。
  • 在T1业务执行结束后开启线程T2,T2执行结束开启线程T3,这样做的好处是如果T1发起了阻塞,那么影响的只有T1这个线程,T2、T3都还没有创建。
  • 公平锁执行任务。

多线程中的伪共享

指的是在多线程环境中,对多CPU中的变量进行写操作,由于缓存一致性协议会导致缓存行全部失效,然后引起的周边变量查询效率低下。解决的方案:数据填充,一个缓存行有64字节,可以将数据进行填充,一个变量独享一个缓存行,在1.8后可以在类中添加注解@contended,表示对数据填充。

简述什么是线程局部变量

属于每个线程中独有的一个属性,比如ThreadLocal,ThreadLocal中维护者自己的空间ThreadLocalMap,保证每一个线程中都有属于自己的变量副本。

线程安全是什么、如何解决

在多线程并发的场景下,线程对公共资源的操作,没有保证操作的原子性、顺序性和其他线程的可见性,导致的数据不一致问题;解决:1. 同步执行加锁 2.atomic原子类 3.线程安全的类

++ 操作符是不是线程安全的

不是线程安全的,因为++操作不是原子的,可以通过同步执行,加锁的方式保证线程安全。

如何去支撑高并发场景

  • 提高每次请求的响应速度,如SQL调优、缓存、接口优化
  • 提高并发效率,使用多线程执行任务
  • 通过架构,微服务,负载均衡分摊给各个节点
  • 通过硬件方面

线程有几种创建方式?

  • 继承Tread重写run方法
  • 实现Runnable接口
  • 实现Callable接口(不同的是有返回值、可以抛出异常)
  • 通过连接池获得

线程的生命周期

  • 新建状态 (new Thread)
  • 运行状态 (t.start)
  • 等待状态 (wait、join)
  • 超时等待 (sleep(100)、join(100))
  • 阻塞状态 (没有持有监视器锁,造成阻塞)
  • 终止状态 (run方法执行结束)

Thread类中的start()和run()方法有什么区别?

start方法是开启一个线程去执行任务,任务的内容是run方法,直接执行run方法不会开启线程,而是使用当前线程来执行方法。

如何唤醒一个阻塞的线程

  • wait 可以通过notify、notifyAll
  • Lock的lock方法可以通过unlock
  • 如果因为Thread.sleep、Object.wait,可以通过interrupt唤醒

线程池的好处?

连接池是享元设计模式的一种具体落地,核心思想在于连接复用,因为线程是一种资源密集型操作,频繁的创建和销毁非常影响系统性能,连接池有以下几点好处。

  1. 避免频繁的创建、销毁线程
  2. 对线程进行复用
  3. 控制线程数量(核心数量、最大数量)
  4. 提供阻塞队列存储任务
  5. 提供拒绝策略

线程池有几种创建使用方式?

  • 线程池工厂Executors
  • TreadPoolExcutor
  • 声明式注解 @EnableAsync  @Async

线程池中的七大参数

  • corePoolSize 核心线程池数量
  • maximumPoolSize 最大线程池数量
  • keepAliveTime 线程存活时间
  • timeUnit 线程存活单位
  • worKQueue 工作队列
  • threadFactory 线程工厂
  • handler 拒绝策略

线程池的执行流程

  1. 如果线程池数量小于核心线程池数量,创建线程执行任务,通过阻塞队列的take方法阻塞、并尝试获取任务。
  2. 如果线程池数量不小于核心线程池数量,那么新的任务会存储到阻塞队列中。
  3. 如果阻塞队列满了装不下了,会继续创建线程,知道最大线程池数量。
  4. 如果线程池到了最大数量,仍然有任务,拒绝策略生效,对任务进行拒绝。

线程池的执行原理

  1. 涉及新建线程:通过for自旋+cas的方式不断地去尝试创建Worker对象,
  2. 如何执行并实现线程复用:通过while自旋的方式,不断地去处理任务,并且尝试从阻塞队列中获得任务,会根据当前线程数量,来决定当前的worker对象是否回收。

ThreadLocal是什么?

ThreadLocal是Java中线程的一个机制,为每个线程都提供一块空间,每个线程都可以独立操作自己的变量副本,互不干扰,避免多线程并发操作同一共享资源造成数据安全问题。

ThreadLocal的实现原理了解吗?

每个线程中都存在一个ThreadLocal变量,ThreadLocal变量中都维护着自己的ThreadLocalMap存储着自己的变量副本,所以副本之间互不干扰,值得一提的是,ThreadLocalMap中是通过entry数组来实现的,每个entry对象的key为当前的ThreadLocal的引用地址,Value为变量副本。

ThreadLocal之间可以实现跨线程的访问、修改吗?

不可以,原因是每个Thread中都有自己的ThreadLocalMap,相互隔离,并且使用set、get方法的时候,会默认的去获取当前的Thread,然后获取Thread中ThreadLocal所以无法访问、修改;Thread中的属性InheribleThreadLocal,它的子线程会继承父线程的ThreadLocalMap,但是如果涉及到修改的话,子线程会先将副本赋值到自己的ThreadLocalMap中,再做更改。

ThreadLocal是如何解决Hash冲突的?

ThreadLocal中如果发生了Hash冲突,它是通过开放寻址法中的线性探测法来解决的,通过向后寻找entry为null的位置插入,除了这种方式应对Hash冲突,链地址法(HashMap)、再Hash法、建立公共溢出区。

锁升级机制

在Java1.6后,Java引入了锁升级机制,synchronized根据线程间的竞争激烈情况,选择不同的锁进行锁升级。

  • 偏向锁:没有发生线程竞争时,对象会对线程记录标记为偏向锁,下次执行可以直接获取锁。
  • 轻量级锁:多个线程竞争一个锁时,会升级为轻量级锁,会通过不断自旋+cas的方式去尝试获取锁,通过短时间的忙等待避免线程切换带来的消耗。
  • 重量级锁:轻量级锁的长时间的自旋是比较耗费CPU资源的,所以当轻量级锁自旋到了一定次数,会升级为重量级锁,此时线程阻塞更节省资源。

自旋锁是什么?

通过不断循环的去做某件事,等待循环结束的时机,通过短时间的忙等待,避免线程阻塞频繁的切换线程上下文带来的消耗。

可重入锁是什么?

首先第一点,同一个线程可以多次的获取同一个锁,第二点:支持锁的嵌套,Java中的ReenTrantLock。

死锁是什么?

死锁指的是多个线程同时访问多个加了互斥锁的公共资源,因为彼此都持有对方所需要的资源,并且都在等待对象执行结束释放资源,造成的相互阻塞状态,此时没有外力介入,那么他们会一直僵持下去。

如何避免N个线程同时访问N个互斥资源所造成的死锁

  • 资源排序法:要求使用资源必须按照指定的顺序依次执行,可以避免死锁。
  • 尝试锁定法:通过tryLock( )方法实现,当线程不能获取锁时,他会释放已经持有的锁。

关于J.U.C

J.U.C是什么?

J.U.C是Java基础类库中java.util.concurrent包,用来解决Java中多线程并发编程问题的技术。

什么是 Java 的并发编程?为什么需要并发编程?

并发编程指的是,用多线程来同时执行多个任务的技术,目的是可以充分利用计算机的多核处理器,提高程序的执行效率。

J.U.C中都包括什么?

  • 线程安全集合,CurrentHashMap、CopyOnWriteArrayList
  • Locks锁,ReenTrainLock、ReentrantReadWriteLock
  • Atomic原子类,AtomicInteger、AtomicLong
  • BlockingQueue阻塞队列、ArrayBlockingQueue、LinkedBlockingQueue
  • 同步辅助类,CountDownLatch

什么是 Java 的内存模型?它如何影响多线程程序的执行?

什么是JMM,定义了程序中变量的访问规则,核心概念有主内存和工作内存,主内存中存储了所有的变量数据,而工作内存,每个线程都有一个工作内存,用于存储变量副本,线程对变量的操作都要在工作内存中进行。

对多线程程序的影响有:1.指令重排序问题,编译器处理器为了优化性能,肯能会对指令进行重排序,在多线程环境中可能导致意外的行为,可以使用volatile关键字 2.原子性问题,有一些复合操作比如i++,不是原子的在多线程环境中可能导致线程安全问题,可以通过同步加锁的方式保证原子性 3.可见性问题,线程对变量进行了修改,其他的线程没有及时收到通知,可能导致数据不一致的问题,可以使用volatile解决可见性问题。

请介绍 Java 中的 volatile 关键字的作用和实现原理。

作用有1.禁止指令重排序 2. 保证其他线程可见性 

原理:根据JMM内存模型,线程的读写操作都要在工作内存上进行,再多线程环境中,如果一个线程对变量进行了修改没有通知到其他线程,那么就会造成数据不一致问题,volatile做的事情就是,当线程对数据修改完毕后,立即通知到主内存,每个线程多该变量的对操作都要直接访问主内存。

什么是 Java 中的锁?请比较 synchronized 和 ReentrantLock 的区别和适用场景。

锁是用于控制多线程并发访问共享资源的机制。

  • 使用上:隐式锁底层jvm实现的;显示锁。

  • synchronized如果发生异常会释放锁,而ReenTrantLock不会。

  • lock可以相应中断,而ReenTrantLock不会。

  • 非公平锁;通过参数可实现公平、非公平锁。

  • 性能上:1.6之前有区别,1.6后synchronized引入了锁升级机制。

使用场景:隐式锁适用于简单的同步场景,而显示锁功能多更灵活,适合复杂的场景。

ReentrantLock多线程lock方法流程

多条线程只有一条线程可以获得乐观锁,线程会更改锁的状态记录当前占有锁的线程,其他没有抢到锁的线程,会通过addWaiter方法包装成一个Node节点,并被组织成一个双向链表,按照循序等待唤醒,执行的条件:1.当前链表节点的前节点是头节点,2.通过cas的方式将状态从0改成1。

如果当前抢到锁的线程发生异常无法执行后面怎么办? 

AQS是什么?

抽象队列同步器,是一个抽象类,其中有很多的模板方法,帮助实现锁、同步工具。

将ReentrantLock中阻塞的线程唤醒,线程可以获取到锁吗?

具体要看线程所在的Node节点在双向链表的位置,如果当前节点的前节点是head头节点,那么它的状态是可以抢夺锁的状态,否则仍然自旋进入阻塞。

JUC 中的锁有哪些?它们的区别是什么?

  • ReenTrantLock: 可重入锁,支持公平、非公平锁。
  • ReenTrantReadWriterLock: 读写分离,适合读多些少。
  • LockSupport: 底层的工具类,提供park、unpark方法阻塞、唤醒线程。
  • condition: 与Lock配合使用,实现复杂的等待、唤醒机制。

请介绍 Java 中的 Condition 接口的作用和使用方式。

作用于线程间的相互协作、通信,await()、signal(),相比于Object中的wait、notify,Condition中提供了更加强大的功能,比如说Condition 可以精确的唤醒哪些条件的线程。

什么是 Java 中的线程池?请分析其优点和使用场景。

通俗来讲就是存储线程的一个池子,是享元模式的一个具体落地,之所以需要线程池是因为线程是一个资源密集型的操作,频繁的创建、销毁非常的消耗系统的性能,而线程池主要的核心就是线程复用,除了这点还有一下几点好处,例如第一,线程池还可以控制池中的数量,设置核心、最大线程池数量、第二,线程池可以提供了工作队列,如果当前的线程池数量达到了核心线程池数量,那么任务任务会存储再工作队列中,第三,提供了线程工厂,我们可以自定义线程名称,可以去区分业务,第四,提供了拒绝策略,如果当前线程达到了最大线程数量,那么对接下来的请求会用拒绝策略进行拒绝。使用场景:后台执行的任务,比如记录日志

什么是 Java 中的并发容器?请举例说明其使用方式和实现原理。

Java中的并发容器指的是应用在多线程并发环境中的容器,比如ConcurrentHashMap,相比于HashMap做了解决了并发安全的问题,比如说数据丢失问题、扩容的问题,ConcurrentHashMap在扩容上的性能也大大提高了,采用的是多线程辅助扩容。

解决数据丢失:当没有发生Hash冲突的时候,通过无锁cas的方式尝试添加数据,当发生哈希冲突的话,会使用synchronized代码块锁住当前哈希同的方式提高并发效率。

解决多线程扩容问题:首先当线程正在扩容时,会锁住原数组的哈希桶,扩容完毕后会更改节点到原数组上标记扩容完成。

什么是CAS?

比较和交换算法,属于一种乐观锁,核心在于,在执行某个操作之前,会比较当前值是否为期望值,如果是,则更新数据,否则不更新,在Java底层代码中通常与自旋配合,通过短时间的忙等待,避免因为频繁阻塞切换线程上下文带来的开销。

JUC 中的线程池有哪些类型?

 FixedThreadPool:固定线程池、CachedThreadPool:缓存线程池、SingleThreadPool:单例线程池、ScheduledThreadPool:定时线程池等,以上是Executors线程池工厂提供的线程池,其实底层使用的还是ThreadPoolExecutor,ThreadPoolExceutor有七大参数,核心、最大线程数量,存活时间、单位,工作队列,线程工厂,拒绝策略,Executor不过是帮我们填好了参数。

JUC 中的 CountDownLatch 是什么?怎么使用?

是Java中J.U.C下的一个同步工具,作用是可以等待多个线程执行完毕后再开始执行某个操作,本质上就是一个计数器。

  1. 新建CountDownLatch对象,初始化默认计数值
  2. 每个线程执行完毕,调用方法递减计数器
  3. 主方法调用await方法进入阻塞,等待计数器归零被唤醒

请谈谈你对 Java 中的并发控制工具类(如 Semaphore、CyclicBarrier、CountDownLatch 等)的理解和实际应用经验。

JUC 中的 Semaphore 是什么?怎么使用?

Semaphore是一种信号量,可以限制同时访问特定资源的线程数量。

  1. 新增Semaphore对象,初始许可证数量。
  2. 当需要访问共享资源时需要获取许可证数量。
  3. 如果获取不到则阻塞知道获取到。
  4. 执行操作完共享逻辑,释放许可证。

JUC 中的 BlockingQueue 是什么?有哪些实现方式?

BlockingQueue支持在多线程环境中实现的生产者-消费者模式的阻塞队列。

  • ArrayBlockingQueue,基于数组实现
  • LinkedBlockingQueue,基于链表实现的有界、无界阻塞队列。
  • PriorityBlockingQueue,基于堆实现的,可以根据元素的优先级进行排序。

JUC 中的 FutureTask 是什么?怎么使用?

用于接受线程异步执行结果的类,参数可以传递继承Runnable或者Callable接口的任务,通过get方法获取执行结果,如果获取不到则一直阻塞。       

  1. 新增FutureTask,参数为实现了Callable的任务。
  2. 创建线程池,通过线程池执行futureTask对象。
  3. 调用futureTask的get方法,方法会阻塞获取执行结果。

JUC 中的 CyclicBarrier 是什么?怎么使用?

同步辅助工具,它允许一组线程相互等待,直到到达规定的数量,可以触发一段操作。

  1. 新增CyclicBarrier,初始化规定数量、执行操作。
  2. 没段线程完成操作后,进行await。
  3. 当await数量达到规定的数量,执行定义的操作。

Java 中的 CountDownLatch 和 CyclicBarrier 类的使用方式和实现原理。

  • CountDownLatch是让一个或者多个线程等待其他线程,其他线程执行完毕才开始执行。原理:内部维护了一个计数器,调用await方法会阻塞,直到计数器为0。
  • CyclicBarrier可以使所有的线程都达到一个公共屏障点,然后这些线程可以继续执行,与CountDownLatch不同的是,可以重置屏障点。原理:内部使用了ReenTraintLock与Condition实现的等待和唤醒。

JUC 中的 Phaser 是什么?怎么使用?

JUC 中的 ThreadLocal 是什么?有什么作用?

ThreadLocal他是线程类中的一个局部变量,他为每个线程都提供属于自己的空间,内部管理着ThreadLocalMap,各线程之间的变量副本互不干扰;作用:避免了多线程同时访问同一共享资源带来的数据安全问题。另外ThreadLocalMap内部使用过Entry数组来实现的,数组长度默认为16,得到key的hasCode与长度-1得到下标,找到位置生成Entry对象插入,Entry对象它的key值是当前的ThreadLocal,value值是副本变量,key值是弱引用的,虽然ThreadLocal也做了防止内存泄漏的操作,但是为了避免内存泄漏,可以说手动的用完副本删除掉;

ThreadLocal的使用场景

  • 保存上下文信息,例如数据库连接管理,不希望频繁的创建数据库连接,可以将数据库连接存储进ThreadLocal中,保证每个线程都有自己的数据库连接
  • 线程安全工具,可以一些非线程安全的工具,例如SimpleDataFormat。

JUC 中的 Lock 接口常用的方法有哪些?怎么应用到实际开发中?

lock、unlock、tryLock,在实际开发中,使用到具体的实现类ReenTraintLock,应用在多线程并发环境中用于保证方法同步执行的,确保同一时间点只有一个线程可以执行方法、访问资源,保证操作的原子性,避免线程安全问题,同样可以用sychronized来实现。

JUC 中的 AtomicInteger 是什么?有什么作用?

AtomicInteger是java.util.concurrent包下提供的原子类,底层通过自旋 + cas无锁的方式保证数据安全,通过短时间的忙等待,避免频繁阻塞、频繁切换线程上下文造成的资源消耗。

JUC 中的 CopyOnWriteArrayList 是什么?有什么作用?

相当于线程安全的ArrayList,每次更改数据前都会复制一份副本,在副本上进行修改,修改完毕将引用指向新的数组,由于写操作不会改变原有数组,所以读操作可以在没有加锁的情况下执行,数和读多写少的场景。

JUC 中的 LockSupport 是什么?有什么作用?

提供了基本的线程阻塞、唤醒等功能,相较于wait、notify,LockSupport更加灵活,不需要应用在同步方法、同步代码块中,并且可以精确的唤醒某个线程,ReenTrantLock底层就运用了LockSupport中的park、unPark实现了线程的阻塞和唤醒。

JUC 中的 Exchanger 是什么?有什么作用?

JUC 中的 ForkJoinPool 是什么?有什么作用?

ConcurrentHashMap的初始化容量是多少,什么时候会扩容,如何扩容?

关于初始化容量:

如果没有设置初始容量的话默认设置为16,如果设置了初始化容量会向上取2的n次幂。

关于扩容的时机:
  1. 当链表长度大于等于8且数组长度小于等于64会优先扩容,如果长度大于64会树化。
  2. 当ConcurrentHashMap当前容量大于总容量*负载因子也会触发扩容,负载因子默认0.75。
  3. 使用putAll时容量不足会发生扩容。
关于如何扩容:

创建一个新数组,容量为原先的2倍,通过多线程分段迁移旧数组中的元素到新数组中,具体扩容的方式与HashMap不同的是,采用多线程辅助扩容。

具体的扩容步骤

  1. 生成扩容标识戳,二进制16位一定是1,左移16位+2,赋值给SIZECTL(负数表示的是正在扩容,并记录参与扩容的线程数量;正数表示下次扩容的阈值)
  2. 计算出每个线程处理多少个哈希桶(最少处理16),新增数组,大小为原数组的2倍。
  3. 线程从后往前领取任务并执行迁移,迁移是锁住哈希桶,迁移完毕将头节点更改,包含新数组的引用、hash值为MOVED,表示该数据桶已经迁移完毕。
  4. 其他线程进行put操作时,如果发生Hash冲突,hash值为MOVED,通过cas的方式抢夺成功协助扩容,抢夺失败返回新的数组引用进行下一步插入操作。
  5. 最后一个完成迁移的线程,将原table的引用指向新table,并且更改SIZECTL扩容状态信息。

为什么扩容时要自旋的去插入数据呢?

因为数组此时可能正在扩容,并且没有完成扩容,所以需要阻塞,当哈希桶扩容完毕,需要重新获取新数组的位置,进行数据插入。

ConcurrentHashmap 的key,value是否可以为null。

都不可以

  • 从代码上:put方法接收到参数需要进行校验,如果key或者value为null,则会抛出nullPointException;get方法提层在拿到key值时先会从key获取hashCode进行扰动函数运算,由于key为null,会引发nullPointException。
  • 从逻辑上:ConCurrentHashMap在多线程环境中,没有办法向HashMap那样通过containsKey判断是key值为null、还是key对应的值为null

ConcurrentHashMap的数据结构是怎么样的

ConcurrentHashMap在1.8中是通过数组+链表+红黑树实现的,具体是一个Node数组,如果发生Hash冲突,会使用链地址法在Node节点的next上新增Node节点形成链表,如果链表长度达到8且数组长度大于64,那么会从链表转化成红黑树来提高查询效率,当链表小于等于6时会从红黑树退化为链表,节省维护树结构的开销。

ConcurrentHashMap的put过程是怎样的?

  1. 首先ConcurrentHashMap的key和value不能为null,对key的hashCode进行扰动函数运算,降低Hash冲突。
  2. 初始化Node数组,默认大小16。
  3. 判断数组索引位置是否为空,如果为空:使用cas的方式插入位置;如果不为空:通过synchronized锁住头节点,使用尾插法形成单向链表。
  4. 根据链表长度决定是否需要扩容、转换成红黑树
  5. 添加元素数量、检查是否需要扩容

ConcurrentHashMap的get过程是怎样的?

  1. 首先对key值的hashCode进行扰动函数运算,降低Hash冲突概率。
  2. 根据计算出的 hash 值和当前数组的长度,确定元素在数组中的位置。
  3. 根据元素的hash值,如果与当前位置的hash相同,则直接返回val;如果hash值为负数,则通过红黑树寻找;否则遍历链表查询

ConcurrentHashMap是如何计算它的size大小的?

  • 线程竞争不激烈的情况,直接用cas (baseCount+1)。
  • 线程竞争激烈的情况,采用数组的方式来分段计数,来降低线程之间的竞争。

JUC 中1.8 的 ConcurrentHashMap为什么可以实现高并发?

  • 数组+链表/红黑树
  • 无锁更新,如果没有发生哈希冲突,则使用cas的方式插入数据。
  • 多线程辅助扩容,ConcurrentHashMap的扩容数据转移操作由多个线程共同完成,利用扩容标识戳记录线程扩容状态,将数组分为不同的段,每个线程负责一段,提高数据扩容的效率。

ConcurrentHashMap使用什么技术来保证线程安全?

cas乐观锁+synchronized同步锁的方式保证了线程安全

  • 数据的覆盖问题:如果没有发生哈希冲突,使用cas的方式尝试插入数据;如果发生哈希冲突,那么使用synchronized的方式覆盖、插入数据。
  • 扩容问题:利用扩容标识戳,记录线程扩容状态,将数组分成不同段的任务,第一个线程创建新数组,每个线程领取任务,最后的线程全面检查一遍有没有遗漏在老数组中的数据。

ConcurrentHashMap的get方法是否要加锁,为什么?

 ConcurrentHashMap迭代器是强一致性还是弱一致性?HashMap呢?

关于集合容器

HashMap的初始容量是多少

  • 如果不传递初始参数的话,初始容量为16。
  • 如果传递参数的话,初始容量为向上取2的n次幂,第一,方便扩容,第二,HashMap为了减少哈希冲突的概率,会对key的hashcode使用扰动函数运算,计算下标就要长度与hashCode进行位运算,容量而如果不取2的n次幂,极有可能使扰动函数失效。

HashMap容量为什么总是2的n次幂

  1. 方便扩容
  2. 为了减少Hash冲突,HashMap对key的hashcode进行了扰动函数,计算下标就要长度与hashCode进行位运算,如果不使用2的n次幂,会导致扰动函数失效。

HashMap是如何扩容的

当创建HashMap是可以设置扩容因子,如果不设置则默认为0.75,如果当前容器数量到达最大容量的0.75,那么会发生扩容,扩容为原先大小的2倍,由于容器大小为2的n次幂,所以扩容非常方便,原数据扩容后不是在原下标就是在原下标+扩容大小的位置上。

HashMap怎么解决Hash冲突的

  • HashMap会对key的hashCode使用扰动函数,通过高低16的异或运算,降低发生hash冲突的几率。
  • 如果发生hash冲突的话,HashMap会使用链地址法的方式解决,会在对应的哈希桶位置使用尾插法形成链表,如果链表长度大于8,并且数组长度大于64,那么链表会转换成为红黑树,来提高查询效率。

HashMap的达到什么条件会从链表、红黑树之间相互转换

当链表长度大于8且数组长度大于64时,会从链表转换为红黑树,来解决链表过长查效率低下的问题;当链表小于6的时侯,从红黑树转化成链表,解决写操作效率低的问题。

HashMap会造成什么并发问题?

  • 因为覆盖导致的数据丢失
  • 因为结构变化导致报错ConcurrentModificationException (并发修改异常)

关于JVM

JVM是什么?

JVM是java虚拟机,是Java语言实现跨平台的重要基石,JVM主要负责程序的具体执行,对类进行初始化,为程序运行提供内存空间、垃圾回收、将class字节码解释成机器码。

JVM中都包括什么?

  • 类加载子系统(负责将类读取到内存中、对类校验、类初始化)
  • 运行时数据区(负责为程序运行提供空间、记录代码执行顺序)
  • 程序执行引擎(负责将字节码解释成机器码以及垃圾回收)
  • 本地库接口(负责调用其他语言)

了解JVM内存模型吗?

  • 方法区(负责存储类信息、常量池)
  • 堆(负责存储对象信息)
  • 方法栈(存储执行方法所需要的参数,局部变量、方法调用和返回等)
  • 本地栈(支持本地方法的调用,通常用于调用c或c++)
  • 计数器(负责记录代码执行行数,唯一不会内存溢出)

对JVM调优有了解吗?

JVM调优,因为内存溢出导致垃圾回收器激活,引发STW导致程序停顿,主要部分是堆内存,堆内存中采取分区分代思想,目的是减少每次GC的扫描范围,当年轻代eden、幸存者区空间不足时,引发小GC,当老年代空间不足时,引发FullGC ,调优是为了降低程序卡顿。

CMS如果fullGC频繁的话怎么办?

对象被存储到老年代有两种情况,可以从这两点入手:

1.对象过大,年轻代经过小GC依然存储不下,会存储到老年代。

   ·  尽量避免大对象的产生拆分成小对象

2.对象年代数大于15,存储老年代

   ·  根据情况调整年轻代比例,减缓年代数的增加

3.编写代码规范

   ·  不是所有对象都会被分配到堆中,逃逸算法证实逃逸的对象,可以经过标量替换分配到栈中。

   ·  四大引用概念区分不同对象的生命周期

   ·  避免内存泄漏,列如ThreadLocal

垃圾回收算法有了解吗?

  • 标记清除法(标记存活的对象,将其余对象全部删除,致命缺点:产生内存碎片)
  • 标记复制法(标记存活的对象,复制到一片空间中,清除除此空间外的对象,不会产生内存碎片,需要额外一片空间,适合年轻代)
  • 标记整理法(标记存活的对象,向一端移动、整理,整理完毕清除其余空间,不会产生内存碎片,适合年老代,对象生命周期较长,需要整理的对象少,效率高)

怎么判断对象是否为垃圾呢?

  • 引用计数法:通过对象被引用数量判断,如果引用数量为0则为垃圾,缺点:存在垃圾对象相互引用的情况,其他引用无法指向,这个两个对象就会一直存在于内存中。
  • 可达性分析算法:如果对象无法通过Root对象直接或间接的访问到,则该对象为垃圾对象。

常用的GC收集器

  • CMS: Concurrent Mark Sweep, 用于老年代的并发类的垃圾回收器【用户线程与垃圾回收线程可以同时执行】比较关注的是停顿时间。 --XX:+UseConcMarkSweepGC.
  • G1:可以设置期望停顿时间,但不能太严格,MixedGC:(Young区和部分Old区的GC)。
  • Parallel、Parallel Old: Java1.8中的默认GC,优先处理吞吐量。

GC收集器分类

  • 并行类的收集器:parallel Scanvent、Parallel Old,吞吐量优先
  • 并发类的收集器:CMS、G1,停顿时间小、适用于Web服务器
  • 串行类的收集器:Serial和Seria Old,内存比较小、嵌入式的设备

G1与CMS的区别有什么

  • CMS用于老年代的回收,G1GC可以用在老年代和年轻代。
  • CMS上使用的是分代分区的思想,而G1GC保留了逻辑上的分代分区,物理上使用了
    Region的方式对堆内存进行了划分。
  • CMS基于标记清除算法,会产生内存碎片,G1基于标记整理算法,整体减少垃圾碎片的产生
  • G1可以去设置期望的停顿时间,JVM会自己通过调整达到这个期望时间。

什么情况下使用G1呢?

  • 堆内存中的数据超过了了50%
  • 对象分配率或晋升率差异很大
  • 期望有比较小的停顿时间(长于0.5 ~ 1秒)
  • 内存比较大,大于6GB

关于G1GC的官方调优建议

  1. 不要设置年轻代的大小或者调整年轻代与老年代的比例,会影响到停顿时间(G1GC是可以自定义期望的停顿时间,所以JVM会自动调整年轻代的大小)。
  2. 可以尝试调整停顿时间,但是不能太过严格,因为会增加GC的次数。
  3. 对其他参数做出调整:​​​​​·​​堆内存使用的比例,触发并发的周期;

发生OOM如何进行调优

  1. 找到具体的堆溢出文件,根据堆内存相关信息,判断如何调优JVM。
  2. 分析GC的日志

如何观测内存使用情况

  • jconsole:JDK中自带的可视化检测工具。可以查看java应用程序的运行情况、监控堆信息、永久区使用情况、类加载情况等,在命令行输入:jconsole。
  • Arthas:采用命令交互模式,排查JVM关键问题的利器。

如何导出堆内存溢出文件

  • 手动导出:jmap -dump:format=b,file=heap.hprof PID
  • 发生堆OOM自动导出,JVM参数添加:-XX:+HeapDumpOnOutOfMemoryError                      -XX:HeapDumpPath=heap.hprof

所有的对象都会保存在堆内存中吗?

不是所有对象都会被分配到堆中,逃逸算法证实未逃逸的对象,可以经过标量替换分配到栈中。

如何查看当前JVM使用的是什么垃圾收集器

  • jps -l      jinfo -flag UseG1GC 17764

官方建议收集器

尽量使用JVM默认选择的垃圾回收器,如果无法满足使用需求,可以适当的调整堆内存的空间

  • 如果程序的内存空间小于100MB,可以选择:-XX:+UseSerialGC.
  • 如果程序是单线程的,并且没有停顿时间的要求,那么可以让JVM去自己选择或者自己去选择:-XX:+UseSerialGC.
  • 如果应用程序没有停顿时间的要求,但是更加关注吞吐量的话可以让JVM自己选择或者自己去选择:-XX:+UseParallelGC.
  • 如果有更多停顿时间的要求,那么可以选择:-XX:UseConcMarkSweepGC.         或者                  -XX:+UseG1GC.

关于SpringBoot

SpringBoot是什么?

首先SpringBoot是基于Spring实现的,SpringBoot以一种约定大于配置的理念,帮助Spring来快速的整合资源,所以可以说SpringBoot是一个脚手架。

为什么要用SpringBoot?

  • 快速搭建:约定大于配置,内嵌式服务
  • 资源整合:自动装配、大量Spring Starter,开箱即用
  • 其他功能:Actuator 监控

Spring Boot中的特性

  • EnableAutoCofiguration 自动装配
  • Starter 依赖  依赖于自动装配的计数
  • Actuator 监控,提供了一些endponit,http、jmx形式去访问,健康信息,metrics信息
  • Spring Boot CLI (命令行操作的功能,groovy脚本)

Spring中自动装配(完整的)思路是什么?

  1. 自动装配
  2. 条件装配
  3. 属性自动绑定

@EnableConfiguration是如何完成自动装配的

  • 启动类@SpringBootApplication中包含注解@EnableAutoConfiguration
  • @EnableAutoConfiguration中包含着@Import注解
  • 类中实现了ImportSelector接口
  • 首先根据META_INF中加载META_INF中的spring.factories,文件中指向了要加载的配置类
  • 筛选后进行条件装配,最终返回String数组。

第三方扩展框架也同样会根据SPI机制做出相应的文件。

SpringBoot的启动流程

  • 通过启动类上的main方法启动,首先对SpringApplication初始化,调用run方法
  • 初始化了初始化器和监听器,随后调用run方法
  • 先后经过监听器、初始化器,进入到refresh方法,此方法是Spring实现IOC的核心方法,方法中有个重要的组件 后置处理器,它的职责主要是解析注解,根据注解去生成BeanDefinition对象,比如Bean、AOP代理类、starter依赖的BeanDefinition都是在这里生成的,随后BeanFactory会依据BeanDefinition去创建对象,。
  • 之后监听器、runner回调,启动成功。

总结的来说,SpringBoot的核心是在Spring的基础之上去初始化的,同时也提供了很多周期方法。

Spring类加载的过程讲一讲

  1. 首先SpringBoot由唯一的启动类启动,Spring会默认扫描启动类所在包。
  2. 通过XML或者注解的形式,加载组件类,如果是XML则通过IO读取,注解通过Java反射技术判断是否为组件类。
  3. 通过BeanDefinitionBuilder构建出BeanDefinition对象,对象中包含了类的属性,最终存储到BeanDefinitionMap中。
  4. 由BeanFactory根据BeanDefinition创建Bean对象。
  5. 将Bean对象保存到ApplicationContext中,可以通过getBean()、依赖注入的方式使用。

说说SpringBoot中都有什么注解

  • @SpringBootApplication(唯一的启动类,组合注解)
  • @SpringConfiguration(表示启动类的同时也是配置类)
  • @EnableConfiguration(包含注解@import,引入自动装配类)
  • @ConponentScan(组件扫描的范围是启动类所在包)

SpringBoot核心配置文件有哪几个?他们的区别是什么?

有两个,application、bootstrap .properties/yml,两者都可以重写三方jar包的配置,最大的区别在读取的时机,bootstrap的读取时机较早,一般用在注册中心中,读取远程配置中心中的配置。

Actuator 监控 health 健康检测

Metrics:   /actuator/metrics
  • JVM(垃圾收集器、内存、堆)
  • 系统(运行时间、平均负载、处理器的信息)
  • 线程池信息
  • tomcat对话信息
Loggers:

注意事项:

<!-- 添加依赖 -->
<dependency><groupId>org.springframework.boot</groupId>
      <artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
# 开启端点
management:
  endpoints:
    web:
      exposure:
        include: "*"
    endpoint:
      metrics:
        enabled: true

SpringBoot项目中的application.yml用法

  • @Value: 映射配置文件中的属性值。
  • @ConfigurationProperty: 将配置文件中的属性映射到对象上。

在Spring中我们将类型交给SpringIOC管理的方式有哪些?

  • 基于xml配置文件<bean>
  • 基于注解@Controller、Service、Mapper、Configuration、Repository等
  • @Bean
  • @Import注解
  • FactoryBean接口【getObject()】

关于import的三种注入方式

  • @Import注解   
  • 实现ImportSelect重写方法,返回值数组包含多个类的全路径名称,import此类
  • 实现ImportBeanDefinitionRegistrar,为我们提供了IOC注册器,可以直接将对象添加其中

SpringBoot自动装配原理

通过条件化配置和组件扫描完成

条件化配置:通过@Confitional系列注解,根据条件读取配置类,配置类中配置组件扫描的路径。

组件扫描:通过注解@ComponentScan指定的包下,Spring都会去扫描,并根据注解生成Bean添加到IOC容器中。

SpringBoot热部署

  1. 放开配置 setting->build->compiler    √ Build project automatically
  2. ctrl+shift+alt+/ ->  registry ->  √ compiler.automake.allow.when.app.running
  3. 添加依赖
    <dependency>
          <groupId>org.springframework.boot</groupId>
          <artifactId>spring-boot-devtools</artifactId>
    </dependency>
    
    <build>
            <plugins>
                <plugin>
                    <groupId>org.springframework.boot</groupId>
                    <artifactId>spring-boot-maven-plugin</artifactId>
                    <configuration>
                        <fork>true</fork>
                    </configuration>
                </plugin>
            </plugins>
        </build>

SpringBoot中的异常处理

  1. 在resource/Templates中添加一个error.html即可,给个静态页面。
  2. 使用ExceptionHandler,通常配合SpringMVC的全局异常处理类一同使用。

SpringBoot中的单元测试

  1. @Runwith  @SpringBootTest  直接帮我们把IOC容器加载过来
  2. @Autowired 属性

Spring发展的各个阶段

  • 1.x:采用XML的方式定义 Bean。 
  • 2.5:Component、Controller、RequestMapping。
  • 3.x:去配置化,更加便捷的加载Bean到容器中。
  • 4.x:@Conditional

没有Spring Boot之前的开发步骤

  • 创建一个项目结构(maven/gradle)
  • spring的依赖、springMVC、servlet api 的依赖
  • web.xml,DispatchServlet
  • 启动一个SpringMVC的配置,,Dispatch-Servlet.xml
  • 创建一个Controller 发布一个http请求
  • 发布到 jsp/servlet容器

总结: 配置重量级、重复的模板配置、微服务的萌芽阶段需要轻量级的工具

自定义Banner

网站:http://patorjk.com/software/taag

自定义banner后,在resources目录下创建 banner.txt文件

SpringBoot整合Filter

SpringBoot整合Listener

SpringBoot如何实现文件上传

Spring整合前端文件FreeMarker

返回的是一个ftl文件,最终

关于数据库MySQL

SQL语句的类型

  • DQL:数据查询语句
  • DML:数据控制语句,添加、修改、写
  • DDL:数据结构化操作,create、alter等
  • DCL:数据权限控制语句
  • TCL:事务相关,比如commit、rollback

MySQL的基本架构

  • 连接层:负责连接以及连接相关的配置
  • 服务层 (连接层、解析器、预处理、优化器、执行器)

        · 解析器:负责解析语句,查看是否符合SQL语法,预处理,防止SQL注入,通过减少解析次数来提升性能

        · 优化器:对SQL语句进行优化

        · 执行器:与存储引擎交互,调用存储引擎的API,返回结果

  • 存储引擎层

SQL的执行流程

InnoDB的表空间

  • 独立表空间:每个表都有一个单独文件
  • 系统表空间:系统文件都存储在一起,耦合度很高
  • 通用表空间:指定哪些表达到哪个通用表空间
  • undo log表空间:回滚日志
  • 临时表表空间:临时表只有当前会话可以访问

InnoDB内存磁盘结构

  • 行 格式
  • page 页
  • extent 区:每个区有最少64个页
  • segement 段

内存缓存结构 Buffer Pool

选择 “page页” 进行内存与磁盘交互的,page页是内存磁盘交互的最小单位,原因是如果选择 “行”,200条数据要交互200次,选择“extent区”会导致内存浪费。

预读机制

线性预读:默认56页,最大64页(一个extent区)

随机预读:

内存数据淘汰机制

LRU变种:

  • 当访问页面数据,缓存到BufferPool中,都会放到OldList头部 包括预读页。
  • 当old页中数据被访问,才会放到newList头部。
  • newList中数据会随着新加入的数据而后移,慢慢会被淘汰。

Redo Log 重做日志

因为缓存页一个页是16K,修改一条数据,整个页都需要进行修改,非常消耗性能,所以InnoDB是通过异步的方式去缓存数据的,异步的方式可能就会导致数据丢失,数据的丢失、回复,就是通过Redo Log日志来完成的,而为了做到追求性能的极致,提供了log buffer的概念,通过配置选择性能或者数据一致性。

存储引擎是什么?都有什么存储引擎

由服务端的执行器来调用

InnoDB(数据一致性高,5.5后默认存储引擎)、MyISAM(适合读多写少,无法保证数据一致性但性能高)、Memory(性能最高,但无法保证数据一致性)

联合索引的原理

首先会根据最左边的字段进行排序,如果想的话才会比较第二个字段,因底层的数据结构B+树肯定是要对数据进行一个排序,这也是为什么设计、使用索引要符合左前缀原则的原因。

什么是回表,什么是索引覆盖

什么是索引下推

索引下推解决的问题是多个条件中的后部分条件没有索引,按照传统的方式会直接返回多条数据给服务端,服务端再根据条件进行一个过滤,过滤后根据索引信息去主键索引中获取数据;那么如果使用了索引下推,可以在存储引擎中直接筛选,筛选后去主键索引中查询,避免了多次回表查询。

索引是不是越多越好,我们应该如何使用索引?

  • 索引这个数据结构也是需要占用磁盘空间的
  • 如果索引上的数据涉及到更改,索引结构也同样要变化,会影响修改效率

1.不能使用离散度太高的字段作为索引,2.联合索引符合左前缀原则

组合索引a,b,c 查询条件 c=10 and a=1 and b>=30,是否能用到索引?

不会用到索引,因为创建索引的时候,会先通过a进行排序,如果a字段相等才会使用b进行排序,所以查询条件 c=10 在前面,那么没有基于c的索引,故索引不生效。

另外索引失效的场景:

  • 没有符合最左原则
  • 第一个条件是范围查询,也会导致第二个索引失效
  • 类型存在转换、隐式转换,排序的方式不一样
  • 运算

总来的说,就是看当前索引的查询顺序,与前的建立索引的拍寻顺序是不是一致的,无序就失效

同时另外走不走索引,优化器说的算

我们主键一般建议趋势递增的原因在哪里?

原因还是在底层索引的数据结构B+Tree,原因是底层的树结构,是一定要将数据排序的,那如果随便在中间添加一条数据,那么极有可能会导致树结构重构,而自增的方式,只会导致数据页的增加,不会重构。这也是不建议用UUID的原因,1.字段太大 2.随机性导致树结构重构

在做InnoDB表结构的时候,一些大字段,比如varchar(5000),会独立生成一个附表为什么?

虽然可能不经常大字段进行一个查询,但是主键索引上默认会存储这个字段,导致每条数据的数据量都非常的大,可能会导致底层树结构高度增加,影响查询效率,所以通常会单独进行存储。

我们使用InnoDB的索引为什么要用B+Tree的数据结构

  1. 相比于其他树结构,B+Tree能够以相同的高度存储更多的数据,每个非叶子节点上只存储了索引,而数据统一存储在叶子节点上,所以非叶子节点就能够存储更多的索引了,相同的数据量下,树的高度低,查询效率快
  2. 稳定性,总共就查询3个数据页
  3. 灵活有序性,数据页之间通过双向链表维护,数据页中的每行数据通过单向链表连接,支持范围查询

三层的B+Tree,能够支撑多大的数据量

按照默认页大小,一个页存储16k,每个索引16byte,可以支撑千万级别的数据量,所以保障查询性能单表数据量一般建议不超过2000万。

了解三大范式吗?

三大范式是数据表设计的常用规范,可以有效地保持数据表整洁、避免数据冗余

  1. 第一范式:所有的字段都应当是原子的,即不可分割的。
  2. 第二范式:在第一范式的基础上,非主键字段必须完全依赖于主键字段,不能依赖于部分主键字段(组合主键的情况下)。
  3. 第三范式:在第二范式的基础上,非主键字段不能存在传递依赖关系

因为三大范式会将表拆分的特别零散,查询时经常要使用复杂的表关联查询,所以时常也会牺牲一些磁盘空间,做出一些反范式设计。

索引是什么?

索引是一种数据结构,相当于字典中的目录,可以帮助我们减少底层IO查询次数,提高查询效率,同时索引也会占用额外的内存空间,并且对索引列进行写操作时,速度会变慢,因为涉及到修改索引数据结构。

执行计划的分析

  • select_type索引的查询类型:all 表示全表查询,最少要达到range级别,否则就需要调优。
  • possible_keys:可能用到的索引。
  • key:实际用到的索引。
  • ref:索引的引用关系
  • rows:预估会读取的数据量。
  • Extra:User index (索引覆盖)、Using index condition(索引下推)、Using filefort(需要将数据拿到内存中进行排序、Using index for group by(group用到了索引优化)

优化器的相关的优化

  • count:count(*) ~= count(1)  >  count(字段)  因为count(字段)会判断字段是否为空
  • limit:深分页的问题,首先确保查询的条件有添加索引,1. 可以先确认id的位置,在基于id的位置按照顺序来查找(基于索引查询);2. 通过子查询查询到范围的id,再通过内关联操作进行关联(减少回表查询)。可以考虑缓存、ES等方式,也可以功能上做出一些限制。
  • order by:尽量去走索引,如果不走索引,会将数据读取到内存中进行排序
  • group by:基于索引,单表匹配符合最左原则

慢日志查询优化

  • 数据库层面优化:1. 表结构是否合理,包括字段类型、存储引擎;2. 是否建立合适的索引,是否充分进行索引覆盖,是否用到优化器的相关优化,比如count、limit、order by、group by 
  • 硬件上:1.更高的磁盘、cpu、内存 2.集群,负载均衡的把压力分摊到各个节点

事务间的隔离并发安全问题

  • 脏读:指的是当前事务可以读取到其他事务还有提交的数据。
  • 不可重复读:指的是当前事务先后读取一条数据的结果不一样。
  • 幻读:指的是当前事务按照某一条件先后读取的数量不一致。

非锁定一致性读取:MVCC

锁定一致性读取:LBCC

  • 意向锁:为了避免锁冲突、提高锁的检查效率,加锁是都会对表加上意向锁。
  • 记录所:在索引树的节点添加记录锁,表示对改记录加锁
  • 间隙锁:在索引树的节点添加间隙锁,表示对当前节点到上一节点这一区间区间内的数据无法添加、删除,但是可以修改。解决了幻读问题
  • 临键锁:在索引树的节点添加临键锁,表示当前节点以及当前节点到上一节点区间都会加锁,其实就是记录所+间隙锁。

BinLog是什么?

BinLog是一个二进制的文件,作用有两个,主从复制和数据恢复,文件中采用存储命令+数据的方式对数据做记录,默认也是开启的。

Binlog与Redo Log的区别

  • Binlog是Mysql服务层的,不管换成哪个存储引擎,Binlog日志都会记录。redolog是InnoDB中的,是为了保证持久性和一致性的。
  • Binlog是一个二进制的文件,采用追加写的方式,做主从复制,redolog是一个物理日志,记录着准确的数据更改时间,采用覆盖写的方式,短时间的数据恢复。
  • 更新数据时记录到redo log,提交事务时记录到Binlog

两者都有存在的必要,并且要保证事务一致性,要么全部写成功,要么全部写失败,否则会造成主从数据不一致问题。

三大日志

  • InnoDB中的二个事务日志

Redolog:因为缓存机制Buffer pool的存在,交互的最小单位是page页,每页默认有16k文件,如果修改一条数据那就要更新1页的数据,所以非常消耗资源,InnoDB采取了异步持久化的方式,为了防止数据丢失,会先将数据记录在redolog中,两个好处:1.保证数据一致性,防止数据丢失 2.一条数据一条数据更新,速度更快。3.不会占用太多空间,采用覆盖写的方式,写前会刷脏。

Undolog:记录了每次提交后的数据,会自动生成事务id,以及上一数据版本的指向,主要用作数据回滚,同时MVCC也用到了undolog,配合readview视图读,完成了非锁定一致性。

  • 服务器曾的日志

binlog:二进制的日志,通过记录命令+行数据的方式,可以去主从同步、大数据恢复

如何实现主从复制,读写分离

  1. 创建数据同步的用户
  2. 设置从库为只读 set GLOBAL read_only = 1;
  3. 创建主从关系 CHANGE MASTER TO...
  4. 开启主从同步 start replica;  -- 开启主从同步、查看从库信息show replica status \G

主从集群的必要两个条件:

1. 实例id,server_id不能相同

2. 主库必须开启binlog,从库可以不开启,但如果这个从库想成为另外一个实例的数据源,也就是将数据同步给另一个库,那就要开启

主从同步方式

异步同步:主库开启异步线程去dump binlog日志文件传送给从库,不会等待数据同步的结果,性能高,但可能发生数据不一致的问题

半同步:就是主库必须等待从库接收到binlog、并记录后返回一定数量的响应,主库才允许提交事务,数量可以设定,可以保证数据一致性,但性能较差。

主从一致性不同步、同步慢问题问题

  • 网络延迟:
  • 主库负载很高:
  • 大事务:
  • 从库机器配置跟不上:

都有什么索引?

按数据结构来讲

  • B+Tree索引
  • Hash索引

按照存储形式来讲

  • 聚簇索引
  • 非聚簇索引

按照类型来讲

  • 主键索引
  • 非空索引
  • 唯一索引
  • 普通索引
  • 组合索引
  • 全文索引

怎么创建索引

  • 创建表时
  • create index index_name on table(column)
  • alter table table_name add index index_name on column

索引失效的场景

  • 条件数据类型存在隐式转换
  • in后判断条件过多
  • like左模糊查询
  • 使用or连接条件要保证条件字段全部添加索引
  • 条件中使用聚合函数

什么是索引覆盖、回表查询

索引覆盖指的是:所有要查询的字段都可以通过索引来获得,回表扫描指的是:索要查询的字段无法直接通过非聚簇索引获取,要经过二次查询。

事务四大特性

原子、一致、持久、隔离

事务的隔离性有了解吗

事物的隔离级别指的是,事务之间的对彼此的影响程度,一般:读未提交、读已提交、可重复读、串行化。

事务之间是怎么做到隔离的

通过读已提交、可重复读通过MVCC技术实现,串行化通过间隙锁实现

什么是MVCC

多版本并发控制,通过undo log日志实现,每次事务提交后都会存储到undo log日志中,包含事务ID、上一版本ID,多条记录构成一个版本链。

如何实现读已提交、可重复读的?

通过MVCC技术、Read View 视图读,视图读主要是为了判断当前事务可以读取版本链中的哪个版本的数据, 在事务开启时生成read view数据,主要包含 当前最小活跃ID、最大ID、所有活跃中的ID,与版本链进行比对,选取可以读取的数据。

关于Spring

能说一说Spring吗?

Spring是一个资源整合的框架,是Spring家族的基石,Spring通过底层的两大核心IOC、AOP完成资源整合的,IOC指的是控制反转,将对象的控制管理权从我们手中移交给Spring容器,它可以更好的对外提供服务,提高对象的利用率,降低系统资源消耗,AOP指的是面向切面编程思想,在程序运行时对核心业务功能进行增强扩展的同时,实现核心业务代码与非核心业务代码的解耦,提高程序扩展性可内聚性。

Spring如何完成组件类加载的

Spring可以通过XML/注解的方式获取组件类,XML:IO流读取xml文件,经过处理得到包含类的属性BeanDefinition对象,存储到Map中,注解:通过Java反射计数通过字节码对象得到类上的注解,符合条件生成BeanDefinition对象,存储到Map中,由BeanFactory的实现工厂根据BeanDefinition创建出对象,最终存储到IOC容器ApplicationContext中,可以通过getBean()取出,或者依赖注入使用。

Spring中的注解知道多少

@Controller、@RestController、@Service、@repostory、@Configuration、@Component、@Bean、@Autowired、@Scope、@Lazy、@PostConstruct、@PreDestroy

依赖注入的方式

属性注入(在属性上添加注解@Autowried,代码整洁)

构造方法注入(通过构造方法自动注入属性,在对象创建时就已经完成初始化)

Setter方法注入(通过Setter方法自动注入属性,可以更改依赖属性)

Spring框架中都用到了什么设计模式?

  • 单例模式(Bean默认是单例的)
  • 简单工厂模式(BeanFactory)
  • 代理模式(AOP的动态代理)
  • 建造模式(通过BeanDefinitionBuilder构建BeanDefinition对象)
  • 适配器模式(HandlerAdapter)
  • 方法模板模式(JdbcTemplate,其中包含访问数据库的模板方法)

说说Spring AOP,以及具体的应用

Spring AOP是Spring中的一个核心模块,是一种面向切面的编程思想,不需要修改代码就可以对核心业务功能进行扩展的,底层使用了动态代理,AOP中有几个重要概念,切面、切入点、连接点、通知类型,可以通过XML或者注解的形式,对核心功能进行增强的同时实现核心业务代码与非核心业务代码的分离解耦。

关于Redis

Redis是什么,有什么作用?

Redis是一个基于内存的非关系型数据库,采用K、V结构存储数据,通常被用作分布式缓存提高查询效率,保护关系型数据库,也可以用来计数、分布式锁等。

Redis中的数据结构有了解吗?

  • String(Value部分存储一个字符串,可以实现计数、分布式锁、序列化)
  • List(Value部分存储的是List集合,有序可重复,适合存储一对多关系的数据)
  • Hash(Value部分存储的是Map,适合存储属性经常变动的对象)
  • Set(Value部分存储的是Set集合,无需不可重复,适合求并集、交集的场景)
  • ZSet(Value部分存储的是可排序的Set集合,通过分值决定排名顺序,适合排行榜)

Redis的持久化方式了解吗?

RDB(快照形式)和AOF(文件末尾追加命令)

Redis数据过期删除策略

定时删除:每隔一段时间,会抽取部分数据,查看是否过期、是否删除。

惰性删除:每次取数据时会查看数据是否过期,如果过期则删除数据,返回null。

Redis中的全局命令

Keys、type、expire、flashall、del

关于消息队列

消息队列是什么?

消息队列可以看作是一个异步通信的组件,消息队列有三个角色构成,分别是生产者、存储端、消费者,经常用在微服务中做异步调用。

有哪些使用场景/好处?

  • 业务解耦(A调用B,B执行失败不影响A成功)
  • 异步通信(提高响应速度、系统吞吐量)
  • 流量削峰(根据消费者能力消费信息)

使用过程中存在什么问题?如何解决的

消息丢失、重复消费问题

首先消息队列有三部分,分别是生产者、存储端、消费端,保证在每一部分都不丢失就可以了。

  • 生产者:同步发送消息的方式,要确认消息成功发送并确认返回结果。
  • 存储段:同步刷盘,当消息持久化成功后,才会返回成功响应。
  • 消费者:在消息消费成功后,做出标记,防止未消费或者重复消费(记录表中)。

关于SpringMVC

说说你对SpringMVC的理解

SpringMVC是一个Web层的框架,是MVC架构设计的一个落地实现,将复杂的问题简单化,分而治之,通过内部组件相互配合,处理请求的接受和响应。

SpringMVC中的常用注解

@RequestMapping、@PostMapping、@GetMapping、@PutMapping、@PatchMapping、@DeleteMapping、@RequestBody、@RequestParam、@RequetBody、@PathVarible、@RequestPath、@RestControllerAdvice

SpringMVC的处理请求流程你了解吗?

WebRequest->Filter->DispatcherServlet->HandlerMapping->HandlerAdapter->HandlerIntercepter->Controller->

关于Mybatis

Mybatis是什么?

是一个优秀持久层框架,Mybatis可以通过注解或者XML的方式对数据库增删改查操作,封装了繁杂的JDBC操作。

有什么优势?

  • 实现了Java代码与Sql语句分离。
  • 便于DBA进行SQL调优
  • 支持动态标签,语句更灵活
  • 将查询结果映射到实体类
  • 提供缓存机制
  • 可扩展性,比如引入PageHelper实现物理分页

简单描述一下Mybatis的工作原理

动态标签都有什么,能说说执行原理吗?

  • if
  • choose、when、otherwise
  • trim
  • foreach

原理:通过ognl表达式解析动态标签中的条件是否成立,参数通过mapper接口中的方法参数获取,根据判断动态的生成语句。

如果查询语句涉及到一对多、多对一关系怎么解决?

一对多:collection标签

多对一:association标签

详细说说缓存机制

MyBatis存在一级、二级缓存,一级缓存是基于SqlSession的,一个事务共享一个SqlSession,如果想要做到跨SqlSession缓存,就要用到基于namespace级别的二级缓存,当SqlSession缓存完毕后、会缓存到二级缓存中,查询顺序:二级缓存->一级缓存->数据库,可以通过@CacheNameSpace开启二级缓存。

说一说Mybatis都用到了什么设计模式

  • 建造模式:SqlSessionFactoryBuilder,构建SqlSessionFactory对象
  • 简单工厂模式:SqlSessionFactory,创建SqlSession对象
  • 代理模式:为@Mapper注解描述的接口创建动态代理对象
  • 享元模式:连接池druid,HikaryCP
  • 模板方法模式:SqlSessionTemplate
  • 组合模式:动态Sql进行拼接

关于nacos

什么是注册中心?

注册中心提供了哪些功能?

服务的注册中心是如何选型的

说说你对nacos的理解

nacos如果来检测服务状态

nacos注册中心有哪些常用配置

关于dubbo

关于sentinel

什么是限流

为什么要限流

关于gateway

关于seta

  • 7
    点赞
  • 8
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值