Java8的新特性

image.png

HashMap话术

我把我理解的说下。
首先, 先它的底层结构来说,它的底层结构在1.7和1.8版有些不一样 。1.7的时候,它是数组+单链表,然后到1.8的时候,它改成了数组+单链表+红黑树的一种方式。红黑树和其它树其根本区别在于添加/删除操作时完成的旋转操作次数,红黑树旋转只要o1次数,而平衡二叉树需要o(log n)旋转。
然后,当单链表和红黑树之间的转换,它的hash冲突的时候,并且它的单链表的长度大于等于8,它会将单链表转换成红黑树形式存储。它在红黑树的节点的数量,如果是小于等于6的时候,它会重新再转成一个单链表。这是底层结构的一个变化。
另外,关于它hash桶的数量默认是16个,它的负载因子默认是0.75,这个阈值关系到它的扩容。扩容是判断当hash桶的数量(16*0.75=12)大于12的时候,它就会触发扩容,扩容成之前hash桶容量的2倍。它会同乘以2的n次幂,然后它会之前的那些老的元素再次进行一次hash运算填充到新的hash桶里面,重新按照链表或者红黑树的方式再排列起来。 且它不是线程安全的,因为再put的时候多线程会有数据覆盖的可能,另外再1.7的时候,这里还有个线程不安全的原因是,它再put的时候还有个resize的过程,这个过程中会由于采用的是头插法,会形成一个环形链表导致一直死循环。1.8的时候,将其改成尾插法了。如果要保证其线程安全,采用ConcurrentHashMap来替代。

ConcurrentHashMap话术

  1. 我把我理解的说下。<br />首先,按底层结构来说,它的数据结构再1.7版底层是个分片数组,为了保证其线程安全,它有个分段锁(Segment)锁,如Segmentget操作实现非常简单和高效。先经过一次哈希,然后使用这个哈希值通过哈希运算定位到segment,再通过哈希算法定位到元素。这个Segment继承于ReentrantLock来保证它的线程安全的。它每次只给一段加锁来保证它的并发度,另外在1.8的时候,它底层改成了与HashMap一样的数据结构,数组+单链表+红黑树的一种结构,在1.8版它逐渐放弃了这种分段(Segment)锁机制,而使用的是SynchronizedCAS来操作。因为对于1.6版的时候,JVMSynchronized的优化非常大,现在也是用这种方法保证它的线程安全。

CAS话术

它啊,见名知义,CAS,Compare And Swap,比较并替换。
CAS相当于一个轻量级加锁的过程,比如说在并发量不大的情况下,锁竞争不激烈,我们要修改一个东西。先查,查完在修改,在准备写之前,我们在查一次,比较之前的结果有没有区别,如果有区别,说明这个修改是不安全的,没有区别,说明这个修改是安全的,这时候它可以去安全的去修改。而不是直接加锁的那种形式,在低并发的情况下,性能会好很多。但当并发量特别大的时候,它始终会有个忙循环的过程,对CPU的性能消耗还是比较大的,当高并发的时候还是建议使用状态机,锁之类的。另外它可能会产生ABA的问题,就是之前读和再过段时间再读中间会被第三个人修改过,但是它又给改回来了这个问题。但是针对这个问题,我们可以采用添加一个AtomicStampedReference或者标志位来解决。

Synchronized话术

关于Synchronized的,它的使用方式,可以用在同步代码块,当然它的同步代码块是可以指定任意对象作为锁,当它应用于非静态方法的时候,它的锁就是this。如果用静态方法则锁定的是它的class对象。关于Synchronized在JDK1.6时升级还是蛮大的。
首先是无锁状态,另外它的偏向锁,再是它的轻量级锁,最后到重量级锁。
一般情况下,在偏向锁过程中,它的意思就非常见名知义,它就偏向于获得第一个锁的线程。它会将线程id写到这个锁对象的对象头当中。
当其它线程来的时候,它就会立马结束这个偏向状态,进而跑到一个轻量级锁。轻量级锁的话,它是在低并发的情况下,来消除锁的。它主要是在虚拟栈中开辟一个空间叫做Lock Record,将锁对象的Mark Word写入,然后再将Lock Record的指针使用CAS去修改锁对象头的那个区域来完成的一个加锁的过程,它也比较普遍应用于一个低并发的情况。
再往上,锁竞争非常激烈,那它就会立马膨胀为一个重量级锁,重量级锁它用的是一个互斥锁的过程来做的。它具体的加锁过程,重量级锁它主要的实现原理。分场景:
同步代码块:在它编译之后,会在你的代码块前后加上两个指令,一个是monitorenter和monitorexit。一个线程来时,它发现这个对象头中,它的锁标志位是无锁,应该是01的状态,然后它会尝试给一个互斥锁对象,这个锁对象会跟另外一个锁对象关联,就是监视器锁monitor,然后它会在monitor的一个锁定器加1,并且将那个monitor的指针写入到一个对象头中,并且修改它的锁对象标志位为10,就是它重量级锁的一个标志位。以此来完成一个换锁的过程。并且它这个过程是可重入的,因为它不用每次出去,进来在加锁还要再释放锁。它每次进来获取这个锁,让锁记录加1就可以了。它加锁完之后,当其它线程来的时候,它会检查到这个锁对象头中,monitor监视器锁上的计数器不为0,它就会在monitor监视状态下等待去竞争这个锁。如果之前结束了操作,它就退出开始释放这个锁,并且逐步将加上的锁释放几次直到将计数器清零来完成对锁的一个释放。让其它线程继续去竞争这个锁。
同步方法:它不是这种指令,而是采用ACC_SYNCHRONIZED标志位,相当于一个flag,当jvm检测到这样一个flag,它自动去走了一个同步方法调用的策略。
每次谈到synchronized我们就会想到reentrantLock。它们两对比的话,区别还是蛮大的。
首先,从jvm层面上,synchronized是jvm的一个关键字,reentrantLock是一个类。我们使用它的时候要手动去编码。像synchronized使用时候是比较简单的。我直接同步代码块或者直接同步方法,我也不需要关心锁的释放。但reentrantLock,我需要手动Lock,然后配合try finally代码块一定要去把它的锁给释放。
另外,reentrantLock相比Synchronized有几个高级特性,它提供了一个,如果有一个线程长期等待不到一个锁的对象,为了防止死锁,你可以手动的去调用一个lockInterruptibly方法去尝试让它释放这个锁,释放自己的资源不去等待,然后,reentrantLock它提供了一个构建公平锁的方式,它的构造函数有一个但是不推荐使用,它会让reentrantLock等级下降。此外它还提供了一个condition,可以让我们指定去唤醒绑定到condition身上的线程,来实现选择性通知的一个机制。这是他们的一个区别。

JUC话术

CountDownLatch,CyclicBarrier,Semaphore
像CountDownLatch适合单线程在运行时,有一段过程你又希望并发的去执行后来回归到一个单线程状态,它适合一个线程等待一批线程达到一个同步点之前去进行。它是不能重用的。它对数字的减法如何保证的?是采用AQS来保证的。
另外CyclicBarrier,它是一个类似CountDownLatch,但它没有一个线程的等待,它是一批线程同时到达一个临界点之后往下走,它里面的好处是。CyccliBarrier可以重用。而CountDownLatch,当计数值为0时,CountDownLatch就不可再用了。
CountDownLatch,CyclicBarrier的区别:
1、CyclicBarrier的某个线程运行到某个点后停止运行,直到所有线程都达到同一个点,所有线程才会重新运行;
CountDownLatch线程运行到某个点后,计数值-1,该线程继续运行,直到计数值为0,则停止运行;
2、CyclicBarrier只能唤醒一个任务;CountDownLatch可以唤醒多个任务;
3、CyccliBarrier可以重用,CountDownLatch不可重用,当计数值为0时,CountDownLatch就不可再用了。

Volatile话术

volatile是java提供最轻量级的一个关键字。具体实现内存的可见性和防止指令的重排序。
首先,得说到我们的计算机模型,cpu和内存之间的线程效率是差好多个数量级的。 但为了保证他们之间的计算,不影响cpu的计算,然后中间像有很多L1L2L3那种缓存,而我们线程在这个缓存中工作,首先它取数据会从主内存取到工作内存中计算,计算完之后再传过去,这就会出现一个问题,多个线程之间的可见性是如何保证的。再计算机层面是有好多协议,在jvm上的中,它为了解决这些比较复杂的东西,它提供了像JMM的这种模型。像被Volatile修饰的一个变量,它就可以保证这个变量在所有线程间的可用性,在这个线程在修改这个变量之后,它可以立马刷到主内存(采用的是总线嗅探机制,会导致它的总线占用资源过大,形成总线风暴),并且它在使用时会立刻从主内存中读出来刷新的那个值。但volatile它是不能保证变量的原子性。像自增自减这种操作他是不能保证的。如果要保证的话,可以使用AutomicInteger,Synchronized,ReentrantLock来保证。
JMM内存模型:
JMM 全称 Java Memory Model, 是 Java 中非常重要的一个概念,是Java并发编程的核心和基础。JMM 是Java 定义的一套协议(MESI),用来屏蔽各种硬件和操作系统的内存访问差异,让Java 程序在各种平台都能有一致的运行效果。
协议MESI:M(Modified):修改;E(Exclusive):独占;S(Shared):共享;I(Invalid):无效
JMM 协议就是一套规范,具体的内容为:所有的变量都存储在主内存中,每个线程还有自己的工作内存,线程的工作内存中保存了该线程使用到的变量(主内存的拷贝),线程对变量的所有操作(读取、赋值)都必须在工作内存中进行,而不能直接读写主内存中的变量。不同线程之间无法直接访问对方工作内存中的变量,线程间变量值的传递均需要在主内存来完成。
JMM有以下规定:
所有的共享变量都存储于主内存,这里所说的变量指的是实例变量和类变量,不包含局部变量,因为局部变量是线程私有的,因此不存在竞争问题。
每一个线程还存在自己的工作内存,线程的工作内存,保留了被线程使用的变量的工作副本。
线程对变量的所有的操作(读,取)都必须在工作内存中完成,而不能直接读写主内存中的变量。
不同线程之间也不能直接访问对方工作内存中的变量,线程间变量的值的传递需要通过主内存中转来完成。
volatile使用原则:

  • 对变量的写操作不依赖于当前值。例如 i++ 这种就不适用。
  • 该变量没有包含在具有其他变量的不变式中。

    线程池话术

    线程池它是由一个核心线程数,最大线程数,空闲时间,单位,缓存队列,工厂方法,拒绝策略
    如果在线程启动的时候,如果你没有设置预启动加载,它默认线程数为0,当你提交一个新任务的时候,它会首先建立一个核心线程,然后去执行任务,如果要是一直来任务,它之前的也没有执行完,那么你会一直建核心线程,当达到最大核心线程数时,如果还都在忙,那么就会放到阻塞队列里面作为节点,如果阻塞队列也放满了,同时核心线程还都在忙,那就会建立非核心线程,直到达到非核心线程数max access,就会触发一个拒绝策略。jdk内置了4种拒绝策略(抛异常,丢弃,重试,丢弃最早提交的)。

    Mysql话术

    调优思路:
    首先,得满足三范式,1范式:字段原子性,字段不可再分割,2范式:即在表中加上一个与业务逻辑无关的字段作为主键,3范式:消除对主键的传递依赖
    其次,是存储引擎的选择。如果没有特别的需求,使用默认的innodb即可。
    MyISAM:以读写插入为主的应用程序,比如博客系统、新闻门户网站。
    Innodb:更新(删除)操作频率也高,或者要保证数据的完整性;并发量高,支持事务和外键保证数据完整性。比如OA自动化办公系统。
    然后,就是索引的选择,字段避免用null,where,order by, group by,join,最左匹配原则,索引选择器(非重复的数据行和重复的数据行,大的放前小的放后这样的形式),多个索引的索引列,建立联合索引而不是单个索引,覆盖索引避免回表操作,like “%”,索引会失效。尽量少用反向判断…索引失效。还有一点,数据查询非常频繁,采用覆盖索引,因为覆盖索引直接就能查询到数据,相对来说还是很快的。
    最后,explain执行优化

Mysql事务:
首先,innoDb的特性就是支持事务,为了保证事务的并发度,它提供了一个隔离级别。它有4个隔离级别。
第一:读未提交,结果什么都不保证的
第二:读已提交,为了解决事务之间的脏读,但它对于你的不可重复读还有幻读,它都是不能保证的
第三:MySql默认的可重复读,这个隔离级别可以保证不发生脏读和不可重复读,但是它不能保证幻读。
第四:序列化。串行化的,它是没有事务的,它是安全性最高的一个级别。
关于解决幻读,你需要手动的去操作,我们知道,select语句是不加锁的,但是我们可以在后面加上,当我们是索引范围查询的时候,在后面加上for update锁住未出现的那个行来保证我们的事务种不读到其它事务中提交的数据行。
关于事务另外一个问题就是丢失更新。关于丢失更新,我们需要手动去处理一下。有两种方式:一种是乐观锁,一种是悲观锁的形式。当乐观锁的时候,可以使用一个类似并发的CAS,version字段的概念。先读,读完之后你改,改的时候在判断下。跟之前版本号比较,如果一致的话则安全写回。另外就是,加个悲观锁,等人查询的时候,带上一个for update给那行上个锁,来防止丢失更新。
mvcc:
其中有两种(读已提交和可重复读)是完全靠mvcc多版本并发控制去保证的。
在理解MVCC原理之前需要先了解两个重要概念:版本链、ReadView。
版本链:
在InnoDB引擎中,每行记录的后面会保存两个隐藏的列:trx_id、roll_pointer。它们的作用如下:
trx_id:用于保存每次对该记录进行修改的事务的id。
roll_pointer:存储一个指针,指向这条数据记录上一个版本的地址,可以通过它获取到该记录上一个版本的数据信息。
ReadView:
MySQL中实现读已提交和可重复读的区别就在于它们生成的ReadView的策略不同。ReadView中主要就是有个列表存储着我们系统中当前活跃着的读写事务,即begin了但还未提交的事务。
那么存储这个列表有什用呢?比如当前ReadView的列表中存储着两条活跃的事务,它们的id分别为20和40:{20,40},有如下规则:
若某事务试图去访问id为10对应的版本数据,它比列表中最小的事务id20还要小,说明它在很早之前就提交了,那么这个id为10的事务对应的版本数据是可以访问的。
若某事务试图去访问id为30对应的版本数据,它大小介于列表中活跃事务id之间,此时就要判断它是否在列表中,若在,则说明还未提交不能访问,若不在,则说明已经提交可以访问。
若某事务试图去访问id为50对应的版本数据,它比列表中最大的事务id40还大,说明它是在生成ReadView之后才发生的,可能还没提交,所以不能访问。
总结:
最后比较这两种隔离级别的区别,发现根本在于它们生成ReadView的策略不同,读已提交每次查询时都会生成一个新的ReadView,而可重复读每次查询都复用第一次生成的ReadView,然后分别按照ReadView的访问规则最终实现读已提交和可重复读。

undo log,redo log,binlog

Spring话术

生命周期:

我理解的是:bean的实例化–>bean的初始化–>bean的使用–>bean的销毁
image.png
image.png
1步:实例化:也就是new一个对象
2步:属性注入:Spring上下文对实例化的bean进行配置(IOC注入)
3步:设置beanId:如果实现BeanNameAware接口,调用setBeanName()方法设置ID
4步:调用BeanFactoryAware.setBeanFactory(setBeanFactory(BeanFactory):可以用这个方式来获取其它Bean,只需在Spring配置文件中配置一个普通的Bean就可以;
5步:调用ApplicationContextAware.setApplicationContext(ApplicationContext):与BeanFactoryAware.setBeanFactory同样作用,但是ApplicationContextAware是子接口,可以实现更多接口;
6步:实例化之前调用:BeanPostProcessor.postProcessBeforeInitialization(Object obj, String s)方法调用,
7步:实例化:如果在spring配置中还配置了init-method属性,会自动调用该方法;
8步:实质化之后调用:如果关联BeanPostProcessor接口,调用postProcessAfterInitialization(Object obj, String s)方法,
9步:注:前面这里我们就完成bean的实例化;
10步:bean的销毁:当bean不再被使用时,就会调用destroy()方法;
11步:bean销毁调用方法:如果配置了destroy-method方法,会自动调用该方法;

spring的事务隔离级别
首先,它的事务隔离级别跟mysql差不多,只不过多了一个默认的隔离级别。
应用层是提供了一个注解,叫做Transactional。
并且它提供了一个事务7种传播行为。
AOP的实现方式:
1种:静态代理
2种:动态代理;又细分一个实现接口方式,走的是JDK动态代理,主要使用java反射机制生成一个代理接口的匿名类,调用具体方法的时候调用invokeHandler。另外一个是cjlib字节码技术去创建代理类对象。主要使用asm字节码编辑技术动态创建类 基于classLoad装载,修改字节码生成子类去处理。
spring是如何解决循环依赖的:
首先,采用的是三级缓存,但是它仅仅能解决的是属性注入的方式,构造器方式解决不了。
它每一级缓存存储的是:
一级缓存,存储的是所有创建好了的单例Bean
二级缓存,完成实例化,但是还未进行属性注入及初始化的对象
三级缓存,提前暴露的一个单例工厂,二级缓存中存储的就是从这个工厂中获取到的对象。
如果使用二级缓存解决循环依赖,意味着所有Bean在实例化后就要完成AOP代理,这样违背了Spring设计的原则,Spring在设计之初就是通过AnnotationAwareAspectJAutoProxyCreator这个后置处理器来在Bean生命周期的最后一步来完成AOP代理,而不是在实例化后就立马进行AOP代理。
还有一点:由于我们放入缓存之后,initializeBean方法中可能存在替换bean的情况,如果只有两级缓存的话这会导致B中注入的A实例与singletonObjects中保存的AA实例不一致,而之后其他的实例注入a时,却会拿到singletonObjects中的AA实例,这样肯定是不符合预期的。

JVM话术

jvm运行时的内存区域:
一般分为两大类,一个是线程的私有区,另一 个是线程的共享区,
对于线程私有,首先就是它程序计数器,这里是在jvm中占内存比较小的一个地方,但是也是唯一一块不会oom的区域。这里存放的是你的代码执行到哪一行,然后上下文切换之后回到原先的状态,另外就是虚拟机栈和本地方法栈。这里虚拟机栈主要使用的是栈帧,方法调用就是入栈到出栈的一个过程。
接着是你的线程共享区,
首先就是方法区,在方法区其实只是jvm的一个规范。 在1.7的时候,它的实现在hotspot虚拟机中叫做永久代。存放的是一些常量池常量还有类的元数据信息。1.8的时候,因为一些原因,它转移到一个集结内存中,它叫做元空间。它存放的是类的元数据信息。这里的方法区也是会触发oom的。
还有一个区域是堆,它存放的是java里产生的对象、对象的实例 ,它也是gc重点回收的一个区域,并且它也是会产生oom的。
最后要关注的就是运行常量池,它是在1.8的时候,将运行常量池转移到堆中,因为之前它在元空间里面,后来搬到这个堆里面来了,堆里面存放的是常量池被加载之后,然后运行时常量池和静态变量都存储到了堆中,这个是整个的运行常量池。
关于直接内存的话,它现在存放的是jvm的元空间,元空间放这里的一个好处就是,oom产生的几率相对之前会变小很多。
OOM的排查思路和过程:
这个的话,可以借助visualVM的工具呀,毕竟在idea方面它是可以直接集成插件的,这个之前用到过,查询它dump下来的堆栈信息排查。
垃圾回收算法:
垃圾回收算法有4个。
第一是标记清除,标记一些不可达的对象,比如说将判定为死亡的对象然后依次抹掉。它会造成很对的内存碎片,并且当它对象是比较大的时候,它标记的效率是比较低的。
判断对象是否应该进行回收又有两种方式,第一就是引用计数,但它的缺点可能会产生循环依赖,第二就是可达性分析算法,根据java中可以作为GC Roots的根节点来开一个链,如果不在这个链上则判断可回收,可以做GC Roots的一些对象,比如像是虚拟机栈中引用的一些对象,还有像方法区中的静态变量所引用的对象,以及常量所引用的对象,还有方法区中所引用的一些对象。
第二种标记复制,就是简单把我们的堆划分成两块,在gc的时候将一些活动对象直接复制到另一半,缺点就是造成内存支持量偏低,但它的好处就是不会产生内存碎片。
第三种标记整理,每次只使用一块区域,将一些已经死亡的对象,往另一端移动、复制然后就就可以找出剩下的一块区域。
第四种分代回收,根据对象存活周期分为不同的几块,新生代,老年代,永久代/元空间,老年代采用该标记清理或者标记整理算法,新生代采用复制算法。
垃圾回收器:
CMS:是可以和用户线程并发操作的,gc线程和用户线程并行执行的一款垃圾回收,它特点就是低延迟,它整个过程是用来回收老年代的,它的算法使用的是标记清除,同时还混合一下标记整理, 它使用标记清除时,它会容忍一定量的垃圾碎片,它垃圾碎片达到一定阈值时,触发一次标记整理来清理一下。它的过程有4个阶段,首先是初始标记,在并发标记,接着是重新标记,最后是并发清理。当它在初始标记和重新标记的时候,它不是线程并行的,它会产生短暂的Stop The World,它的并发标记和并发清理,这两个阶段的耗时是比较长的,但它是和用户线程一起并发执行的。所以说它可以最短的实现的低延迟。
G1:G1收集器将新生代和老年代取消了,取而代之的是将堆划分为若干的区域,弱化了分代,并且它使用的标记整理,不会产生空间碎片,分配大对象不会触发full gc,充分利用cpu多核条件下,可以缩短Stop The World(可以控制)。它的过程也分4个阶段,首先初始标记,在并发标记,接着重新标记,最后并发清理。
CMS和G1的它们两个Stop The World的区别:
cms中STW是不可以控制的,而G1是可以通过cpu核心数控制调整的。
CPU突然飙高了的排查方式:
如果部署在linux,可以通过命令查看cpu飙高对应pid,然后用jstack命令。

设计模式话术

设计模式的原则:
第一:开闭原则:对扩展开放,对修改关闭
第二:里氏替换原则:任何基类可以出现的地方,子类一定可以出现
第三:依赖倒转原则:针对接口编程,依赖于抽象而不依赖于具体。
第四:接口隔离原则:使用多个隔离的接口,比使用单个胖接口要好。
第五:迪米特法则原则:叫最少知道原则,就是说:一个实体应当尽量少的与其他实体之间发生相互作用,使得系统功能模块相对独立
第六:合成复用原则:原则是尽量使用合成/聚合的方式,而不是使用继承
第七:单一职责
熟悉的设计模式:
责任链模式,代理模式。

Redis话术

RocketMq话术


[

](https://blog.csdn.net/sinat_32023305/article/details/109720581)