谈谈ConcurrentHashMap的扩容机制

1.7版本
1. 1.7版本的ConcurrentHashMap是基于Segment分段实现的
2. 每个Segment相对于⼀个⼩型的HashMap
3. 每个Segment内部会进⾏扩容,和HashMap的扩容逻辑类似
4. 先⽣成新的数组,然后转移元素到新数组中
5. 扩容的判断也是每个Segment内部单独判断的,判断是否超过阈值

1.8版本
1. 1.8版本的ConcurrentHashMap不再基于Segment实现
2. 当某个线程进⾏put时,如果发现ConcurrentHashMap正在进⾏扩容那么该线程⼀起进⾏扩容
3. 如果某个线程put时,发现没有正在进⾏扩容,则将key-value添加到ConcurrentHashMap中,然后判断是否超过阈值,超过了则进⾏扩容
4. ConcurrentHashMap是⽀持多个线程同时扩容的
5. 扩容之前也先⽣成⼀个新的数组
6. 在转移元素时,先将原数组分组,将每组分给不同的线程来进⾏元素的转移,每个线程负责⼀组或多组的元素转移⼯作

Jdk1.7到Jdk1.8 HashMap 发⽣了什么变化(底层)?

1. 1.7中底层是数组+链表,1.8中底层是数组+链表+红⿊树,加红⿊树的⽬的是提⾼HashMap插⼊和查询整体效率
2. 1.7中链表插⼊使⽤的是头插法,1.8中链表插⼊使⽤的是尾插法,因为1.8中插⼊key和value时需要判断链表元素个数,所以需要遍历链表统计链表元素个数,所以正好就直接使⽤尾插法
3. 1.7中哈希算法⽐较复杂,存在各种右移与异或运算,1.8中进⾏了简化,因为复杂的哈希算法的⽬的就是提⾼散列性,来提供HashMap的整体效率,⽽1.8中新增了红⿊树,所以可以适当的简化哈希算法,节省CPU资源

说⼀下HashMap的Put⽅法

先说HashMap的Put⽅法的⼤体流程:
1. 根据Key通过哈希算法与与运算得出数组下标
2. 如果数组下标位置元素为空,则将key和value封装为Entry对象(JDK1.7中是Entry对象,JDK1.8中是Node对象)并放⼊该位置
3. 如果数组下标位置元素不为空,则要分情况讨论
    a. 如果是JDK1.7,则先判断是否需要扩容,如果要扩容就进⾏扩容,如果不⽤扩容就⽣成Entry对象,并使⽤头插法添加到当前位置的链表中
    b. 如果是JDK1.8,则会先判断当前位置上的Node的类型,看是红⿊树Node,还是链表Node
        ⅰ. 如果是红⿊树Node,则将key和value封装为⼀个红⿊树节点并添加到红⿊树中去,在这个过程中会判断红⿊树中是否存在当前key,如果存在则更新value
        ⅱ. 如果此位置上的Node对象是链表节点,则将key和value封装为⼀个链表Node并通过尾插法插⼊到链表的最后位置去,因为是尾插法,所以需要遍历链表,在遍历链表的过程中会判断是否存在当前key,如果存在则更新value,当遍历完链表后,将新链表Node插⼊到链表中,插⼊到链表后,会看当前链表的节点个数,如果⼤于等于8,那么则会将该链表转成红⿊树
        ⅲ. 将key和value封装为Node插⼊到链表或红⿊树中后,再判断是否需要进⾏扩容,如果需要就扩容,如果不需要就结束PUT⽅法

泛型中extends和super的区别 

1. <? extends T>表示包括T在内的任何T的⼦类
2. <? super T>表示包括T在内的任何T的⽗类

HashMap的扩容机制原理

1.7版本
1. 先生成新数组
2. 遍历⽼数组中的每个位置上的链表上的每个元素
3. 取每个元素的key,并基于新数组⻓度,计算出每个元素在新数组中的下标
4. 将元素添加到新数组中去
5. 所有元素转移完了之后,将新数组赋值给HashMap对象的table属性

1.8版本
1. 先生成新数组
2. 遍历⽼数组中的每个位置上的链表或红⿊树
3. 如果是链表,则直接将链表中的每个元素重新计算下标,并添加到新数组中去
4. 如果是红⿊树,则先遍历红⿊树,先计算出红⿊树中每个元素对应在新数组中的下标位置
    a. 统计每个下标位置的元素个数
    b. 如果该位置下的元素个数超过了8,则⽣成⼀个新的红⿊树,并将根节点的添加到新数组的对应位置
    c. 如果该位置下的元素个数没有超过8,那么则⽣成⼀个链表,并将链表的头节点添加到新数组的对应位置
5. 所有元素转移完了之后,将新数组赋值给HashMap对象的table属性

CopyOnWriteArrayList的底层原理是怎样的

1、首先CopyOnWriteArrayList内部也是⽤过数组来实现的,在向CopyOnWriteArrayList添加元素时,会复制一个新的数组,写操作在新数组上进行,读操作在原数组上进行
2. 并且,写操作会加锁,防止出现并发写入丢失数据的问题
3. 写操作结束之后会把原数组指向新数组
4. CopyOnWriteArrayList允许在写操作时来读取数据,大大提高了读的性能,因此适合读多写少的应用场景,但是CopyOnWriteArrayList会比较占内存,同时可能读到的数据不是实时最新的数据,所以不适合实时性要求很高的场景

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

Java中的编译器和解释器:Java中引⼊了虚拟机的概念,即在机器和编译程序之间加⼊了⼀层抽象的虚拟的机器。这台虚拟的机器在任何平台上都提供给编译程序⼀个的共同的接⼝。编译程序只需要⾯向虚拟机,⽣成虚拟机能够理解的代码,然后由解释器来将虚拟机代码转换为特定系统的机器码执⾏。在Java中,这种供虚拟机理解的代码叫做 字节码(即扩展名为 .class的⽂件),它不⾯向任何特定的处理器,只⾯向虚拟机。
    每⼀种平台的解释器是不同的,但是实现的虚拟机是相同的。Java源程序经过编译器编译后变成字节码,字节码由虚拟机解释执⾏,虚拟机将每⼀条要执⾏的字节码送给解释器,解释器将其翻译成特定机器上的机器码,然后在特定的机器上运⾏。这也就是解释了Java的编译与解释并存的特点。
    Java源代码---->编译器---->jvm可执⾏的Java字节码(即虚拟指令)---->jvm---->jvm中解释器----->机器可执⾏的⼆进制机器码---->程序运⾏。
采⽤字节码的好处:
    Java语⾔通过字节码的⽅式,在⼀定程度上解决了传统解释型语⾔执⾏效率低的问题,同时⼜保留了解释型语⾔可移植的特点。所以Java程序运⾏时⽐较⾼效,⽽且,由于字节码并不专对⼀种特定的机器,因此,Java程序⽆须重新编译便可在多种不同的计算机上运⾏。

Java中的异常体系是怎样的

Java中的所有异常都来⾃顶级⽗类Throwable。
Throwable下有两个⼦类Exception和Error。
Error是程序⽆法处理的错误,⼀旦出现这个错误,则程序将被迫停⽌运⾏。
Exception不会导致程序停⽌,⼜分为两个部分RunTimeException运⾏时异常和
CheckedException检查异常。
RunTimeException常常发⽣在程序运⾏过程中,会导致程序当前线程执⾏失败。
CheckedException常常发⽣在程序编译过程中,会导致程序编译不通过。

Java中有哪些类加载器

JDK⾃带有三个类加载器:bootstrap ClassLoader、ExtClassLoader、AppClassLoader。
    BootStrapClassLoader是ExtClassLoader的⽗类加载器,默认负责加载%JAVA_HOME%lib下的jar包和class⽂件。
    ExtClassLoader是AppClassLoader的⽗类加载器,负责加载%JAVA_HOME%/lib/ext⽂件夹下的jar包和class类。
    AppClassLoader是⾃定义类加载器的⽗类,负责加载classpath下的类⽂件。

GC如何判断对象可以被回收

引⽤计数法:每个对象有⼀个引⽤计数属性,新增⼀个引⽤时计数加1,引⽤释放时计数减1,计数为0时可以回收,
    可达性分析法:从 GC Roots 开始向下搜索,搜索所⾛过的路径称为引⽤链。当⼀个对象到 GCRoots 没有任何引⽤链相连时,则证明此对象是不可⽤的,那么虚拟机就判断是可回收对象。
    引⽤计数法,可能会出现A 引⽤了 B,B ⼜引⽤了 A,这时候就算他们都不再使⽤了,但因为相互引⽤ 计数器=1 永远⽆法被回收。21
GC Roots的对象有:
    虚拟机栈(栈帧中的本地变量表)中引⽤的对象
    ⽅法区中类静态属性引⽤的对象
    ⽅法区中常量引⽤的对象
    本地⽅法栈中JNI(即⼀般说的Native⽅法)引⽤的对象
    可达性算法中的不可达对象并不是⽴即死亡的,对象拥有⼀次⾃我拯救的机会。对象被系统宣告死亡⾄少要经历两次标记过程:第⼀次是经过可达性分析发现没有与GC Roots相连接的引⽤链,第⼆次是在由虚拟机⾃动建⽴的Finalizer队列中判断是否需要执⾏finalize()⽅法。
    当对象变成(GC Roots)不可达时,GC会判断该对象是否覆盖了finalize⽅法,若未覆盖,则直接将其回收。否则,若对象未执⾏过finalize⽅法,将其放⼊F-Queue队列,由⼀低优先级线程执⾏该队列中对象的finalize⽅法。执⾏finalize⽅法完毕后,GC会再次判断该对象是否可达,若不可达,则进⾏回收,否则,对象“复活”
    每个对象只能触发⼀次finalize()⽅法
    由于finalize()⽅法运⾏代价⾼昂,不确定性⼤,⽆法保证各个对象的调⽤顺序,不推荐⼤家使⽤,建议遗忘它。

你们项⽬如何排查JVM问题

对于还在正常运⾏的系统:
1. 可以使⽤jmap来查看JVM中各个区域的使⽤情况
2. 可以通过jstack来查看线程的运⾏情况,⽐如哪些线程阻塞、是否出现了死锁
3. 可以通过jstat命令来查看垃圾回收的情况,特别是fullgc,如果发现fullgc⽐较频繁,那么就得进⾏调优了
4. 通过各个命令的结果,或者jvisualvm等⼯具来进⾏分析
5. ⾸先,初步猜测频繁发送fullgc的原因,如果频繁发⽣fullgc但是⼜⼀直没有出现内存溢出,那么表示fullgc实际上是回收了很多对象了,所以这些对象最好能在younggc过程中就直接回收掉,避免这些对象进⼊到⽼年代,对于这种情况,就要考虑这些存活时间不⻓的对象是不是⽐较⼤,导致年轻代放不下,直接进⼊到了⽼年代,尝试加⼤年轻代的⼤⼩,如果改完之后,fullgc减少,则证明修改有效
6. 同时,还可以找到占⽤CPU最多的线程,定位到具体的⽅法,优化这个⽅法的执⾏,看是否能避免某些对象的创建,从⽽节省内存

对于已经发⽣了OOM的系统:
1. ⼀般⽣产系统中都会设置当系统发⽣了OOM时,⽣成当时的dump⽂件(-XX:+HeapDumpOnOutOfMemoryError -XX:HeapDumpPath=/usr/local/base)
2. 我们可以利⽤jsisualvm等⼯具来分析dump⽂件
3. 根据dump⽂件找到异常的实例对象,和异常的线程(占⽤CPU⾼),定位到具体的代码
4. 然后再进⾏详细的分析和调试

总之,调优不是⼀蹴⽽就的,需要分析、推理、实践、总结、再分析,最终定位到具体的问题

⼀个对象从加载到JVM,再到被GC清除,都经历了什么过程?

1. ⽤户创建⼀个对象,JVM⾸先需要到⽅法区去找对象的类型信息。然后再创建对象。
2. JVM要实例化⼀个对象,⾸先要在堆当中先创建⼀个对象。-> 半初始化状态
3. 对象⾸先会分配在堆内存中新⽣代的Eden。然后经过⼀次Minor GC,对象如果存活,就会进⼊S区。在后续的每次GC中,如果对象⼀直存活,就会在S区来回拷⻉,每移动⼀次,年龄加1。-> 多⼤年龄才会移⼊⽼年代? 年龄最⼤15, 超过⼀定年龄后,对象转⼊⽼年代。
4. 当⽅法执⾏结束后,栈中的指针会先移除掉。
5. 堆中的对象,经过Full GC,就会被标记为垃圾,然后被GC线程清理掉

怎么确定⼀个对象到底是不是垃圾?

1. 引⽤计数: 这种⽅式是给堆内存当中的每个对象记录⼀个引⽤个数。引⽤个数为0的就认为是垃圾。这是早期JDK中使⽤的⽅式。引⽤计数⽆法解决循环引⽤的问题。
2. 根可达算法: 这种⽅式是在内存中,从引⽤根对象向下⼀直找引⽤,找不到的对象就是垃圾。

线程的⽣命周期?线程有⼏种状态

线程通常有五种状态,创建,就绪,运⾏、阻塞和死亡状态:
    1. 新建状态(New):新创建了⼀个线程对象。
    2. 就绪状态(Runnable):线程对象创建后,其他线程调⽤了该对象的start⽅法。该状态的线程位于可运⾏线程池中,变得可运⾏,等待获取CPU的使⽤权。
    3. 运⾏状态(Running):就绪状态的线程获取了CPU,执⾏程序代码。
    4. 阻塞状态(Blocked):阻塞状态是线程因为某种原因放弃CPU使⽤权,暂时停⽌运⾏。直到线程进⼊就绪状态,才有机会转到运⾏状态。
    5. 死亡状态(Dead):线程执⾏完了或者因异常退出了run⽅法,该线程结束⽣命周期。

阻塞的情况⼜分为三种:
    1. 等待阻塞:运⾏的线程执⾏wait⽅法,该线程会释放占⽤的所有资源,JVM会把该线程放⼊“等待池”中。进⼊这个状态后,是不能⾃动唤醒的,必须依靠其他线程调⽤notify或notifyAll⽅法才能被唤醒,wait是object类的⽅法
    2. 同步阻塞:运⾏的线程在获取对象的同步锁时,若该同步锁被别的线程占⽤,则JVM会把该线程放⼊“锁池”中。
    3. 其他阻塞:运⾏的线程执⾏sleep或join⽅法,或者发出了I/O请求时,JVM会把该线程置为阻塞状态。当sleep状态超时、join等待线程终⽌或者超时、或者I/O处理完毕时,线程重新转⼊就绪状态。sleep是Thread类的⽅法

sleep()、wait()、join()、yield()之间的的区别

锁池:所有需要竞争同步锁的线程都会放在锁池当中,⽐如当前对象的锁已经被其中⼀个线程得到,则其他线程需要在这个锁池进⾏等待,当前⾯的线程释放同步锁后锁池中的线程去竞争同步锁,当某个程得到后会进⼊就绪队列进⾏等待cpu资源分配。

等待池:当我们调⽤wait()⽅法后,线程会放到等待池当中,等待池的线程是不会去竞争同步锁。只有调⽤了notify()或notifyAll()后等待池的线程才会开始去竞争锁,notify()是随机从等待池选出⼀个线程放到锁池,⽽notifyAll()是将等待池的所有线程放到锁池当中
    1. sleep 是 Thread 类的静态本地⽅法,wait 则是 Object 类的本地⽅法。
    2. sleep⽅法不会释放lock,但是wait会释放,⽽且会加⼊到等待队列中。
        sleep就是把cpu的执⾏资格和执⾏权释放出去,不再运⾏此线程,当定时时间结束再取回cpu资源,参与cpu的调度,获取到cpu资源后就可以继续运⾏了。⽽如果sleep时该线程有锁,那么sleep不会释放这个锁,⽽是把锁带着进⼊了冻结状态,也就是说其他需要这个锁的线程根本不可能获取到这个锁。也就是说⽆法执⾏程序。如果在睡眠期间其他线程调⽤了这个线程的interrupt⽅法,那么这个线程也会抛出interruptexception异常返回,这点和wait是⼀样的
    3. sleep⽅法不依赖于同步器synchronized,但是wait需要依赖synchronized关键字。
    4. sleep不需要被唤醒(休眠之后推出阻塞),但是wait需要(不指定时间需要被别⼈中断)。
    5. sleep ⼀般⽤于当前线程休眠,或者轮循暂停操作,wait 则多⽤于多线程之间的通信。
    6. sleep 会让出 CPU 执⾏时间且强制上下⽂切换,⽽ wait 则不⼀定,wait 后可能还是有机会重新竞争到锁继续执⾏的。
    7. yield()执⾏后线程直接进⼊就绪状态,⻢上释放了cpu的执⾏权,但是依然保留了cpu的执⾏资格,所以有可能cpu下次进⾏线程调度还会让这个线程获取到执⾏权继续执⾏
8. join()执⾏后线程进⼊阻塞状态,例如在线程B中调⽤线程A的join(),那线程B会进⼊到阻塞队列,直到线程A结束或中断线程

Thread和Runable的区别

Thread和Runnable的实质是继承关系,没有可⽐性。⽆论使⽤Runnable还是Thread,都会newThread,然后执⾏run⽅法。⽤法上,如果有复杂的线程操作需求,那就选择继承Thread,如果只是简单的执⾏⼀个任务,那就实现runnable。

会卖出多⼀倍的票

public static void main(String[] args) {new MyThread().start();new MyThread().start();}static class MyThread extends Thread{private int ticket = 5;public void run(){while(true){System.out.println("Thread ticket = " + ticket--);if(ticket < 0){break;}}}}

原因是:MyThread创建了两个实例,⾃然会卖出两倍,属于⽤法错误 

正常的售卖

 public static void main(String[] args) {MyThread2 mt=new MyThread2();new Thread(mt).start();new Thread(mt).start();}static class MyThread2 implements Runnable{private int ticket = 5;public void run(){while(true){System.out.println("Runnable ticket = " + ticket--);if(ticket < 0){break;}}}}

ThreadLocal的底层原理

1. ThreadLocal是Java中所提供的线程本地存储机制,可以利⽤该机制将数据缓存在某个线程内部,该线程可以在任意时刻、任意⽅法中获取缓存的数据
    2. ThreadLocal底层是通过ThreadLocalMap来实现的,每个Thread对象(注意不是ThreadLocal对象)中都存在⼀个ThreadLocalMap,Map的key为ThreadLocal对象,Map的value为需要缓存的值
    3. 如果在线程池中使⽤ThreadLocal会造成内存泄漏,因为当ThreadLocal对象使⽤完之后,应该要把设置的key,value,也就是Entry对象进⾏回收,但线程池中的线程不会回收,⽽线程对象是通过强引⽤指向ThreadLocalMap,ThreadLocalMap也是通过强引⽤指向Entry对象,线程不被回收,Entry对象也就不会被回收,从⽽出现内存泄漏,解决办法是,在使⽤了ThreadLocal对象之后,⼿动调⽤ThreadLocal的remove⽅法,⼿动清楚Entry对象
    4. ThreadLocal经典的应⽤场景就是连接管理(⼀个线程持有⼀个连接,该连接对象可以在不同的⽅法之间进⾏传递,线程之间不共享同⼀个连接)

 并发、并⾏、串⾏之间的区别

1. 串⾏在时间上不可能发⽣重叠,前⼀个任务没搞定,下⼀个任务就只能等着
    2. 并⾏在时间上是重叠的,两个任务在同⼀时刻互不⼲扰的同时执⾏。
    3. 并发允许两个任务彼此⼲扰。统⼀时间点、只有⼀个任务运⾏,交替执⾏

并发的三⼤特性

原⼦性    关键字:synchronized
可⻅性    关键字:volatile、synchronized、final
有序性    关键字:volatile、synchronized     
但是volatile关键字不满⾜原⼦性
synchronized关键字同时满⾜以上三种特性,但是volatile关键字不满⾜原⼦性。
在某些情况下,volatile的同步机制的性能确实要优于锁(使⽤synchronized关键字或java.util.concurrent包⾥⾯的锁),因为volatile的总开销要⽐锁低。
我们判断使⽤volatile还是加锁的唯⼀依据就是volatile的语义能否满⾜使⽤的场景(原⼦性)

Java死锁如何避免?

造成死锁的⼏个原因:
    1. ⼀个资源每次只能被⼀个线程使⽤
    2. ⼀个线程在阻塞等待某个资源时,不释放已占有资源
    3. ⼀个线程已经获得的资源,在未使⽤完之前,不能被强⾏剥夺
    4. 若⼲线程形成头尾相接的循环等待资源关系
 
    这是造成死锁必须要达到的4个条件,如果要避免死锁,只需要不满⾜其中某⼀个条件即可。⽽其中前3个条件是作为锁要符合的条件,所以要避免死锁就需要打破第4个条件,不出现循环等待锁的关系。
在开发过程中:
1. 要注意加锁顺序,保证每个线程按同样的顺序进⾏加锁
2. 要注意加锁时限,可以针对所设置⼀个超时时间
3. 要注意死锁检查,这是⼀种预防机制,确保在第⼀时间发现死锁并进⾏解决

为什么⽤线程池?解释下线程池参数?

1、降低资源消耗;提⾼线程利⽤率,降低创建和销毁线程的消耗。
    2、提⾼响应速度;任务来了,直接有线程可⽤可执⾏,⽽不是先创建线程,再执⾏。
    3、提⾼线程的可管理性;线程是稀缺资源,使⽤线程池可以统⼀分配调优监控。

线程池的底层⼯作原理

线程池内部是通过队列+线程实现的,当我们利⽤线程池执⾏任务时:
    1. 如果此时线程池中的线程数量⼩于corePoolSize,即使线程池中的线程都处于空闲状态,也要创建新的线程来处理被添加的任务。
    2. 如果此时线程池中的线程数量等于corePoolSize,但是缓冲队列workQueue未满,那么任务被放⼊缓冲队列。
    3. 如果此时线程池中的线程数量⼤于等于corePoolSize,缓冲队列workQueue满,并且线程池中的数量⼩于maximumPoolSize,建新的线程来处理被添加的任务。
    4. 如果此时线程池中的线程数量⼤于corePoolSize,缓冲队列workQueue满,并且线程池中的数量等于maximumPoolSize,那么通过 handler所指定的策略来处理此任务。
    5. 当线程池中的线程数量⼤于 corePoolSize时,如果某线程空闲时间超过keepAliveTime,线程将被终⽌。这样,线程池可以动态的调整池中的线程数

ReentrantLock中的公平锁和⾮公平锁的底层实现

⾸先不管是公平锁和⾮公平锁,它们的底层实现都会使⽤AQS来进⾏排队,它们的区别在于:线程在使⽤lock()⽅法加锁时,如果是公平锁,会先检查AQS队列中是否存在线程在排队,如果有线程在排队,则当前线程也进⾏排队,如果是⾮公平锁,则不会去检查是否有线程在排队,⽽是直接竞争锁。
    不管是公平锁还是⾮公平锁,⼀旦没竞争到锁,都会进⾏排队,当锁释放时,都是唤醒排在最前⾯的线程,所以⾮公平锁只是体现在了线程加锁阶段,⽽没有体现在线程被唤醒阶段。

ReentrantLock中tryLock()和lock()⽅法的区别

1. tryLock()表示尝试加锁,可能加到,也可能加不到,该⽅法不会阻塞线程,如果加到锁则返回true,没有加到则返回false
    2. lock()表示阻塞加锁,线程会阻塞直到加到锁,⽅法也没有返回值

Sychronized的偏向锁、轻量级锁、重量级锁

1. 偏向锁:在锁对象的对象头中记录⼀下当前获取到该锁的线程ID,该线程下次如果⼜来获取该锁就可以直接获取到了
    2. 轻量级锁:由偏向锁升级⽽来,当⼀个线程获取到锁后,此时这把锁是偏向锁,此时如果有第⼆个线程来竞争锁,偏向锁就会升级为轻量级锁,之所以叫轻量级锁,是为了和重量级锁区分开来,轻量级锁底层是通过⾃旋来实现的,并不会阻塞线程
    3. 如果⾃旋次数过多仍然没有获取到锁,则会升级为重量级锁,重量级锁会导致线程阻塞
    4. ⾃旋锁:⾃旋锁就是线程在获取锁的过程中,不会去阻塞线程,也就⽆所谓唤醒线程,阻塞和唤醒这两个步骤都是需要操作系统去进⾏的,⽐较消耗时间,⾃旋锁是线程通过CAS获取预期的⼀个标记,如果没有获取到,则继续循环获取,如果获取到了则表示获取到了锁,这个过程线程⼀直在运⾏中,相对⽽⾔没有使⽤太多的操作系统资源,⽐较轻量。

Sychronized和ReentrantLock的区别

1. sychronized是⼀个关键字,ReentrantLock是⼀个类
    2. sychronized会⾃动的加锁与释放锁,ReentrantLock需要程序员⼿动加锁与释放锁
    3. sychronized的底层是JVM层⾯的锁,ReentrantLock是API层⾯的锁
    4. sychronized是⾮公平锁,ReentrantLock可以选择公平锁或⾮公平锁
    5. sychronized锁的是对象,锁信息保存在对象头中,ReentrantLock通过代码中int类型的state标识来标识锁的状态
    6. sychronized底层有⼀个锁升级的过程

Spring是什么?

轻量级的开源的J2EE框架。它是⼀个容器框架,⽤来装javabean(java对象),中间层框架(万能胶)可以起⼀个连接作⽤,⽐如说把Struts和hibernate粘合在⼀起运⽤,可以让我们的企业开发更快、更简洁,Spring是⼀个轻量级的控制反转(IoC)和⾯向切⾯(AOP)的容器框架:
    从⼤⼩与开销两⽅⾯⽽⾔Spring都是轻量级的。
    通过控制反转(IoC)的技术达到松耦合的⽬的
    提供了⾯向切⾯编程的丰富⽀持,允许通过分离应⽤的业务逻辑与系统级服务进⾏内聚性的开发
    包含并管理应⽤对象(Bean)的配置和⽣命周期,这个意义上是⼀个容器。
    将简单的组件配置、组合成为复杂的应⽤,这个意义上是⼀个框架。

谈谈你对AOP的理解

系统是由许多不同的组件所组成的,每⼀个组件各负责⼀块特定功能。除了实现⾃身核⼼功能之外,这些组件还经常承担着额外的职责。例如⽇志、事务管理和安全这样的核⼼服务经常融⼊到⾃身具有核⼼业务逻辑的组件中去。这些系统服务经常被称为横切关注点,因为它们会跨越系统的多个组件。
    当我们需要为分散的对象引⼊公共⾏为的时候,OOP则显得⽆能为⼒。也就是说,OOP允许你定义从上到下的关系,但并不适合定义从左到右的关系。例如⽇志功能。⽇志代码往往⽔平地散布在所有对象层次中,⽽与它所散布到的对象的核⼼功能毫⽆关系。
    在OOP设计中,它导致了⼤量代码的重复,⽽不利于各个模块的重⽤。
    AOP:将程序中的交叉业务逻辑(⽐如安全,⽇志,事务等),封装成⼀个切⾯,然后注⼊到⽬标对象(具体业务逻辑)中去。AOP可以对某个对象或某些对象的功能进⾏增强,⽐如对象中的⽅法进⾏增强,可以在执⾏某个⽅法之前额外的做⼀些事情,在某个⽅法执⾏之后额外的做⼀些事情

谈谈你对IOC的理解容器概念、控制反转、依赖注⼊

容器概念
   1、 ioc容器:实际上就是个map(key,value),⾥⾯存的是各种对象(在xml⾥配置的bean节点、@repository、@service、@controller、@component),在项⽬启动的时候会读取配置⽂件⾥⾯的bean节点,根据全限定类名使⽤反射创建对象放到map⾥、扫描到打上上述注解的类还是通过反射创建对象放到map⾥。
   2、 这个时候map⾥就有各种对象了,接下来我们在代码⾥需要⽤到⾥⾯的对象时,再通过DI注⼊(autowired、resource等注解,xml⾥bean节点内的ref属性,项⽬启动的时候会读取xml节点ref属性根据id注⼊,也会扫描这些注解,根据类型或id注⼊;id就是对象名)。
控制反转

1、没有引⼊IOC容器之前,对象A依赖于对象B,那么对象A在初始化或者运⾏到某⼀点的时候,⾃⼰必须主动去创建对象B或者使⽤已经创建的对象B。⽆论是创建还是使⽤对象B,控制权都在⾃⼰⼿上。引⼊IOC容器之后,对象A与对象B之间失去了直接联系,当对象A运⾏到需要对象B的时候,IOC容器会主动创建⼀个对象B注⼊到对象A需要的地⽅。
    2、通过前后的对⽐,不难看出来:对象A获得依赖对象B的过程,由主动⾏为变为了被动⾏为,控制权颠倒过来了,这就是“控制反转”这个名称的由来。
    3、全部对象的控制权全部上缴给“第三⽅”IOC容器,所以,IOC容器成了整个系统的关键核⼼,它起到了⼀种类似“粘合剂”的作⽤,把系统中的所有对象粘合在⼀起发挥作⽤,如果没有这个“粘合剂”,对象与对象之间会彼此失去联系,这就是有⼈把IOC容器⽐喻成“粘合剂”的由来。
依赖注⼊:
    “获得依赖对象的过程被反转了”。控制被反转之后,获得依赖对象的过程由⾃身管理变为了由IOC容器主动注⼊。依赖注⼊是实现IOC的⽅法,就是由IOC容器在运⾏期间,动态地将某种依赖关系注⼊到对象之中。

Spring事务的实现⽅式和原理以及隔离级别?

1、在使⽤Spring框架时,可以有两种使⽤事务的⽅式,⼀种是编程式的,⼀种是申明式的,
    2、@Transactional注解就是申明式的。
    3、⾸先,事务这个概念是数据库层⾯的,Spring只是基于数据库中的事务进⾏了扩展,以及提供了⼀些能让程序员更加⽅便操作事务的⽅式。
    4、⽐如我们可以通过在某个⽅法上增加@Transactional注解,就可以开启事务,这个⽅法中所有的sql都会在⼀个事务中执⾏,统⼀成功或失败。
    5、在⼀个⽅法上加了@Transactional注解后,Spring会基于这个类⽣成⼀个代理对象,会将这个代理对象作为bean,当在使⽤这个代理对象的⽅法时,如果这个⽅法上存在@Transactional注解,那么代理逻辑会先把事务的⾃动提交设置为false,然后再去执⾏原本的业务逻辑⽅法,如果执⾏业务逻辑⽅法没有出现异常,那么代理逻辑中就会将事务进⾏提交,如果执⾏业务逻辑⽅法出现了异常,那么则会将事务进⾏回滚。
    6、当然,针对哪些异常回滚事务是可以配置的,可以利⽤@Transactional注解中的rollbackFor属性进⾏配置,默认情况下会对RuntimeException和Error进⾏回滚。
    spring事务隔离级别就是数据库的隔离级别:外加⼀个默认级别
        read uncommitted(未提交读)
        Spring事务的实现⽅式和原理以及隔离级别?
        read committed(提交读、不可重复读)
        repeatable read(可重复读)
        serializable(可串⾏化)

Spring事务传播机制

多个事务⽅法相互调⽤时,事务如何在这些⽅法间传播,⽅法A是⼀个事务的⽅法,⽅法A执⾏过程中调⽤了⽅法B,那么⽅法B有⽆事务以及⽅法B对事务的要求不同都会对⽅法A的事务具体执⾏造成影响,同时⽅法A的事务对⽅法B的事务执⾏也有影响,这种影响具体是什么就由两个⽅法所定义的事务传播类型所决定。
       1. REQUIRED(Spring默认的事务传播类型):如果当前没有事务,则⾃⼰新建⼀个事务,如果当前存在事务,则加⼊这个事务
        2. SUPPORTS:当前存在事务,则加⼊当前事务,如果当前没有事务,就以⾮事务⽅法执⾏
        3. MANDATORY:当前存在事务,则加⼊当前事务,如果当前事务不存在,则抛出异常。
        4. REQUIRES_NEW:创建⼀个新事务,如果存在当前事务,则挂起该事务。
        5. NOT_SUPPORTED:以⾮事务⽅式执⾏,如果当前存在事务,则挂起当前事务
        6. NEVER:不使⽤事务,如果当前事务存在,则抛出异常
        7. NESTED:如果当前事务存在,则在嵌套事务中执⾏,否则REQUIRED的操作⼀样(开启⼀个事务)

Spring事务什么时候会失效?

spring事务的原理是AOP,进⾏了切⾯增强,那么失效的根本原因是这个AOP不起作⽤了!常⻅情况有如下⼏种
    1、发⽣⾃调⽤,类⾥⾯使⽤this调⽤本类的⽅法(this通常省略),此时这个this对象不是代理类,⽽是UserService对象本身!解决⽅法很简单,让那个this变成UserService的代理类即可!
    2、⽅法不是public的:@Transactional 只能⽤于 public 的⽅法上,否则事务不会失效,如果要⽤在⾮public ⽅法上,可以开启 AspectJ 代理模式。
    3、数据库不⽀持事务
    4、没有被spring管理
    5、异常被吃掉,事务不会回滚(或者抛出的异常没有被定义,默认为RuntimeException)

Spring中的Bean创建的⽣命周期有哪些步骤

Spring中⼀个Bean的创建⼤概分为以下⼏个步骤:
    1. 推断构造⽅法
    2. 实例化
    3. 填充属性,也就是依赖注⼊
    4. 处理Aware回调
    5. 初始化前,处理@PostConstruct注解
    6. 初始化,处理InitializingBean接⼝
    7. 初始化后,进⾏AOP

Spring中Bean是线程安全的吗

Spring本身并没有针对Bean做线程安全的处理,所以:
      1. 如果Bean是⽆状态的,那么Bean则是线程安全的
      2. 如果Bean是有状态的,那么Bean则不是线程安全的另外,Bean是不是线程安全,跟Bean的作⽤域没有关系,Bean的作⽤域只是表示Bean的⽣命周期范围,对于任何⽣命周期的Bean都是⼀个对象,这个对象是不是线程安全的,还是得看这个Bean对象本身。

ApplicationContext和BeanFactory有什么区别

BeanFactory是Spring中⾮常核⼼的组件,表示Bean⼯⼚,可以⽣成Bean,维护Bean,⽽ApplicationContext继承了BeanFactory,所以ApplicationContext拥有BeanFactory所有的特点,也是⼀个Bean⼯⼚,但是ApplicationContext除开继承了BeanFactory之外,还继承了诸如EnvironmentCapable、MessageSource、ApplicationEventPublisher等接⼝,从⽽ApplicationContext还有获取系统环境变量、国际化、事件发布等功能,这是BeanFactory所不具备的

Spring中的事务是如何实现的

 1. Spring事务底层是基于数据库事务和AOP机制的
    2. ⾸先对于使⽤了@Transactional注解的Bean,Spring会创建⼀个代理对象作为Bean
    3. 当调⽤代理对象的⽅法时,会先判断该⽅法上是否加了@Transactional注解
    4. 如果加了,那么则利⽤事务管理器创建⼀个数据库连接
    5. 并且修改数据库连接的autocommit属性为false,禁⽌此连接的⾃动提交,这是实现Spring事务⾮常重要的⼀步
    6. 然后执⾏当前⽅法,⽅法中会执⾏sql
    7. 执⾏完当前⽅法后,如果没有出现异常就直接提交事务
    8. 如果出现了异常,并且这个异常是需要回滚的就会回滚事务,否则仍然提交事务
    9. Spring事务的隔离级别对应的就是数据库的隔离级别
    10. Spring事务的传播机制是Spring事务⾃⼰实现的,也是Spring事务中最复杂的
    11. Spring事务的传播机制是基于数据库连接来做的,⼀个数据库连接⼀个事务,如果传播机制配置为需要新开⼀个事务,那么实际上就是先建⽴⼀个数据库连接,在此新数据库连接上执⾏sql

Spring中什么时候@Transactional会失效

因为Spring事务是基于代理来实现的,所以某个加了@Transactional的⽅法只有是被代理对象调⽤时,那么这个注解才会⽣效,所以如果是被代理对象来调⽤这个⽅法,那么@Transactional是不会失效的。
    同时如果某个⽅法是private的,那么@Transactional也会失效,因为底层cglib是基于⽗⼦类来实现的,⼦类是不能重载⽗类的private⽅法的,所以⽆法很好的利⽤代理,也会导致@Transactianal失效

Spring容器启动流程是怎样的

    1. 在创建Spring容器,也就是启动Spring时:
    2. ⾸先会进⾏扫描,扫描得到所有的BeanDefinition对象,并存在⼀个Map中
    3. 然后筛选出⾮懒加载的单例BeanDefinition进⾏创建Bean,对于多例Bean不需要在启动过程中去进⾏创建,对于多例Bean会在每次获取Bean时利⽤BeanDefinition去创建
    4. 利⽤BeanDefinition创建Bean就是Bean的创建⽣命周期,这期间包括了合并BeanDefinition、推断构造⽅法、实例化、属性填充、初始化前、初始化、初始化后等步骤,其中AOP就是发⽣在初始化后这⼀步骤中
    5. 单例Bean创建完了之后,Spring会发布⼀个容器启动事件
    6. Spring启动结束
    7. 在源码中会更复杂,⽐如源码中会提供⼀些模板⽅法,让⼦类来实现,⽐如源码中还涉及到⼀些BeanFactoryPostProcessor和BeanPostProcessor的注册,Spring的扫描就是通过BenaFactoryPostProcessor来实现的,依赖注⼊就是通过BeanPostProcessor来实现的
    8. 在Spring启动过程中还会去处理@Import等注解

Spring⽤到了哪些设计模式

 Spring Boot、Spring MVC 和 Spring 有什么区别

 spring是⼀个IOC容器,⽤来管理Bean,使⽤依赖注⼊实现控制反转,可以很⽅便的整合各种框架,提供AOP机制弥补OOP的代码重复问题、更⽅便将不同类不同⽅法中的共同处理抽取成切⾯、⾃动注⼊给⽅法执⾏,⽐如⽇志、异常等
    springmvc是spring对web框架的⼀个解决⽅案,提供了⼀个总的前端控制器Servlet,⽤来接收请求,然后定义了⼀套路由策略(url到handle的映射)及适配执⾏handle,将handle结果使⽤视图解析技术⽣成视图展现给前端
    springboot是spring提供的⼀个快速开发⼯具包,让程序员能更⽅便、更快速的开发spring+springmvc应⽤,简化了配置(约定了默认配置),整合了⼀系列的解决⽅案(starter机制)、redis、mongodb、es,可以开箱即⽤

Spring MVC ⼯作流程

1)⽤户发送请求⾄前端控制器 DispatcherServlet。
    2)DispatcherServlet 收到请求调⽤ HandlerMapping 处理器映射器。
    3)处理器映射器找到具体的处理器(可以根据 xml 配置、注解进⾏查找),⽣成处理器及处理器拦截器(如果有则⽣成)⼀并返回给 DispatcherServlet。
    4)DispatcherServlet 调⽤ HandlerAdapter 处理器适配器。
    5)HandlerAdapter 经过适配调⽤具体的处理器(Controller,也叫后端控制器)
    6)Controller 执⾏完成返回 ModelAndView。
    7)HandlerAdapter 将 controller 执⾏结果 ModelAndView 返回给 DispatcherServlet。8)DispatcherServlet 将 ModelAndView 传给 ViewReslover 视图解析器。
    9)ViewReslover 解析后返回具体 View。
    10)DispatcherServlet 根据 View 进⾏渲染视图(即将模型数据填充⾄视图中)。
    11)DispatcherServlet 响应⽤户。

Spring MVC的主要组件?

Handler:也就是处理器。它直接应对着MVC中的C也就是Controller层,它的具体表现形式有很多,可以是类,也可以是⽅法。在Controller层中@RequestMapping标注的所有⽅法都可以看成是⼀个Handler,只要可以实际处理请求就可以是Handler
    1、HandlerMapping
    initHandlerMappings(context),处理器映射器,根据⽤户请求的资源uri来查找Handler的。在SpringMVC中会有很多请求,每个请求都需要⼀个Handler处理,具体接收到⼀个请求之后使⽤哪个Handler进⾏,这就是HandlerMapping需要做的事。
    2、HandlerAdapter
    initHandlerAdapters(context),适配器。因为SpringMVC中的Handler可以是任意的形式,只要能处理请求就ok,但是Servlet需要的处理⽅法的结构却是固定的,都是以request和response为参数的⽅法。如何让固定的Servlet处理⽅法调⽤灵活的Handler来进⾏处理呢?这就是HandlerAdapter要做的事情。Handler是⽤来⼲活的⼯具;HandlerMapping⽤于根据需要⼲的活找到相应的⼯具;HandlerAdapter是使⽤⼯具⼲活的⼈。
    3、HandlerExceptionResolver
    initHandlerExceptionResolvers(context), 其它组件都是⽤来⼲活的。在⼲活的过程中难免会出现问题,出问题后怎么办呢?这就需要有⼀个专⻔的⻆⾊对异常情况进⾏处理,在SpringMVC中就是HandlerExceptionResolver。具体来说,此组件的作⽤是根据异常设置ModelAndView,之后再交给render⽅法进⾏渲染。
    4、ViewResolver
    initViewResolvers(context),ViewResolver⽤来将String类型的视图名和Locale解析为View类型的视图。View是⽤来渲染⻚⾯的,也就是将程序返回的参数填⼊模板⾥,⽣成html(也可能是其它类型)⽂件。这⾥就有两个关键问题:使⽤哪个模板?⽤什么技术(规则)填⼊参数?这其实是ViewResolver主要要做的⼯作,ViewResolver需要找到渲染所⽤的模板和所⽤的技术(也就是视图的类型)进⾏渲染,具体的渲染过程则交由不同的视图⾃⼰完成。
    5、RequestToViewNameTranslator
    initRequestToViewNameTranslator(context),ViewResolver是根据ViewName查找View,但有的Handler处理完后并没有设置View也没有设置ViewName,这时就需要从request获取ViewName了,如何从request中获取ViewName就是RequestToViewNameTranslator要做的事情了。RequestToViewNameTranslator在Spring MVC容器⾥只可以配置⼀个,所以所有request到ViewName的转换规则都要在⼀个Translator⾥⾯全部实现。
    6、LocaleResolver
    initLocaleResolver(context), 解析视图需要两个参数:⼀是视图名,另⼀个是Locale。视图名是处理器返回的,Locale是从哪⾥来的?这就是LocaleResolver要做的事情。LocaleResolver⽤于从request解析出Locale,Locale就是zh-cn之类,表示⼀个区域,有了这个就可以对不同区域的⽤户显示不同的结果。SpringMVC主要有两个地⽅⽤到了Locale:⼀是ViewResolver视图解析的时候;⼆是⽤到国际化资源或者主题的时候。
    7、ThemeResolver
    initThemeResolver(context),⽤于解析主题。SpringMVC中⼀个主题对应⼀个properties⽂件,⾥⾯存放着跟当前主题相关的所有资源、如图⽚、css样式等。SpringMVC的主题也⽀持国际化,同⼀个主题不同区域也可以显示不同的⻛格。SpringMVC中跟主题相关的类有 ThemeResolver、ThemeSource和Theme。主题是通过⼀系列资源来具体体现的,要得到⼀个主题的资源,⾸先要得到资源的名称,这是ThemeResolver的⼯作。然后通过主题名称找到对应的主题(可以理解为⼀个配置)⽂件,这是ThemeSource的⼯作。最后从主题中获取资源就可以了。
    8、MultipartResolver
    initMultipartResolver(context),⽤于处理上传请求。处理⽅法是将普通的request包装成MultipartHttpServletRequest,后者可以直接调⽤getFile⽅法获取File,如果上传多个⽂件,还可以调⽤getFileMap得到FileName->File结构的Map。此组件中⼀共有三个⽅法,作⽤分别是判断是不是上传请求,将request包装成MultipartHttpServletRequest、处理完后清理上传过程中产⽣的临时资源。
    9、FlashMapManager
    initFlashMapManager(context),⽤来管理FlashMap的,FlashMap主要⽤在redirect中传递参数。

java 高级面试题(借鉴)(上)相关推荐

  1. JAVA高级面试题汇总及答案

    JAVA高级面试题汇总及答案 1.hashaMap原理源码 2.synchronize关键字1.6之后的优化,(偏向轻量级锁,重量级锁) 3.双亲委派是什么 4.类加载过程中可以动态改字节码吗? 5. ...

  2. java高级面试题收集及答案

    2021 java高级面试题收集及答案 以下问题是个人面试时收集,答案纯属个人见解,如有错误请不吝赐教,感激不尽. 这里写目录标题 2021 java高级面试题收集及答案 一级目录 二级目录 三级目录 ...

  3. 2023最新ChatGPT整理的40道Java高级面试题

    2023 年最火的就是 ChatGPT 了,很多同事使用他完成一些代码上的智能提示,也有人使用它发了财<「用ChatGPT年入百万!」各博主发布生财之道,网友:答辩搬运工>.<&qu ...

  4. 【JAVA】2022年JAVA高级面试题汇总

    一.参考资料 不容错过的Java高级面试题_帝都的雁的博客-CSDN博客_java高级面试题 java面试题汇总(上)_Oliverfly1的博客-CSDN博客_java面试题 史上最全的中高级 JA ...

  5. 2021年Java高级面试题总结

    零基础如何学习Java? 首先,你要明白一点,Java入门不难! 无论你是从事哪个行业,兴趣一定是最好的老师,也是你学习的动力. 学习方式1:自学 自学模式其实我个人不建议绝大部分的人选择,因为自学是 ...

  6. 【面试题】-java高级面试题汇总

    花大笔墨整理的一些java高级程序猿必会的面试题,其中有很多都是大厂面试题. 目录 1.spring IOC原理 2.spring bean生命周期 3.spring aop原理 4.spring m ...

  7. 2018年一线互联网公司Java高级面试题总结

    1.hashcode相等两个类一定相等吗?equals呢?相反呢? 2.介绍一下集合框架? 3.hashmap hastable 底层实现什么区别?hashtable和concurrenthashta ...

  8. JAVA中的线程安全与非线程安全,java面试题,java高级笔试题

    写在最前面,我总结出了很多互联网公司的面试题及答案,并整理成了文档,以及各种学习的进阶学习资料,免费分享给大家.扫码加微信好友进[程序员面试学习交流群],免费领取.也欢迎各位一起在群里探讨技术. 转自 ...

  9. 12道Java高级面试题:java时间差计算

    前言 分布式,是程序员必备技能之一,在面试过程中属于必备类的,在工作中更是会经常用到.而Kafka是一个分布式的基于发布订阅的消息队列,目前它的魅力是无穷的,对于Kafka的奥秘,还需要我们细细去探寻 ...

  10. 头歌Educoder——Java高级特性 - JDBC(上)

    第1关:JDBC连接数据库 任务描述 本关任务:使用jdbc连接数据库并完成创建数据库和创建表的操作. 相关知识 JDBC API提供以下接口和类: DriverManager:此类管理数据库驱动程序 ...

最新文章

  1. 句法依存分析_恭喜 | 黑龙江大学自然语言处理实验室获得NLPCC2019跨领域移植中文依存句法分析封闭测试第一名!...
  2. iPhone的录音和播放
  3. zabbix监控suse linux,SuSE 系统之部署 Zabbix 监控服务
  4. Effective Go中文版(更新中)
  5. JS设为首页、添加到收藏夹
  6. Pytorch运行错误:CUDA out of memory处理过程
  7. 小榕密码管家xp 绿色
  8. 信息论的基本概念和熵的计算
  9. 视觉SLAM十四讲 第7讲 (3) 相机运动估计 2D-2D/3D-2D/3D-3D
  10. 企业邮箱安全中心在哪里,TOM邮箱安全设置中心
  11. cad计算机绘图基础知识,机械零件制图识图必须懂的七大基础知识
  12. Python写的Web spider(网络爬虫)
  13. 图片一键添加水印工具V1.0-免费版
  14. 适合长时间佩戴的耳机有哪些、六款适合久戴的运动耳机推荐
  15. 1-5分钟上手自动化测试——Airtest+Poco快速上手
  16. python编程练习--水仙花数
  17. 关于type_info与typeid
  18. 淘宝 聚划算 整点免费 支付宝延时时间1秒
  19. 请叫我En-Tan-Mo
  20. 强制浏览器以IE8版本运行

热门文章

  1. 江苏大学和南邮的计算机专业,江苏最适合普通考生的5所大学,性价比很高,不让考生浪费一分...
  2. WPS表格甘特图制作教程
  3. 小程序 身份认证服务器,如何实现微信小程序与.net core应用服务端的无状态身份验证...
  4. 《十周成为数据分析师》笔记——业务线 第五节 用户画像体系
  5. ipad iphone开发_如何在iPhone或iPad上“不信任”计算机
  6. 微信小程序搭建项目起步
  7. 电动尾门驱动芯片TMI8720-Q1,越来越多的电机已被应用到汽车上
  8. 字符映射表 charmap
  9. Linux内核静态映射表建立过程分析
  10. TensorFlow2.0学习笔记-3.模型训练