1,JVM的内存模型及优点

JVM 内存模型共分为虚拟机栈,堆,方法区,程序计数器,本地方法栈五个部分。

Java 虚拟机栈与程序计数器一样,Java 虚拟机栈(Java Virtual Machine Stacks)也是线程私有的,它的生命周期与线程相同。
Java 堆对于大多数应用来说,Java 堆(Java Heap)是Java 虚拟机所管理的内存中最大的一块。Java 堆是被所有线程共享的一块内存区域,在虚拟机启动时创建。
方法区(Method Area)与Java 堆一样,是各个线程共享的内存区域,它用于存储已被虚拟机加载的类信息、常量、静态变量、即时编译器编译后的代码等数据。
程序计数器(Program Counter Register)是一块较小的内存空间,它的作用可以看
做是当前线程所执行的字节码的行号指示器。
本地方法栈(Native Method Stacks)与虚拟机栈所发挥的作用是非常相似的,其区别不过是虚拟机栈为虚拟机执行Java 方法(也就是字节码)服务,而本地方法栈则是为虚拟机使用到的Native 方法服务。

JVM内存模型优点

内置基于内存的并发模型:线程机制
同步锁Synchronization
大量线程安全型库包支持
基于内存的并发机制,粒度灵活控制,灵活度高于数据库锁。
多核并行计算模型
基于线程的异步模型。

2,java类加载过程

一般来说,我们把 Java 的类加载过程分为三个主要步骤:加载,连接,初始化。

1)加载

首先是加载过程(Loading),它是 Java 将字节码数据从不同的数据源读取到 JVM 中,并映射为 JVM 认可的数据结构(Class 对象),这里的数据源可能是各种各样的形态,比如 jar 文件,class 文件,甚至是网络数据源等;如果输入数据不是 ClassFile 的结构,则会抛出 ClassFormatError。加载阶段是用户参与的阶段,我们可以自定义类加载器,去实现自己的类加载过程。

2)连接

第二阶段是连接(Linking),这是核心的步骤,简单说是把原始的类定义信息平滑地转入 JVM 运行的过程中。这里可进一步细分成三个步骤:

1,验证(Verification)

这是虚拟机安全的重要保障,JVM 需要核验字节信息是符合 Java 虚拟机规范的,否则就被认为是 VerifyError,这样就防止了恶意信息或者不合规信息危害 JVM 的运行,验证阶段有可能触发更多 class 的加载。

2,准备(Pereparation)

创建类或者接口中的静态变量,并初始化静态变量的初始值。但这里的“初始化”和下面的显示初始化阶段是有区别的,侧重点在于分配所需要的内存空间,不会去执行更进一步的 JVM 指令。

3,解析(Resolution)

在这一步会将常量池中的符号引用(symbolic reference)替换为直接引用。在 Java 虚拟机规范中,详细介绍了类,接口,方法和字段等各方面的解析。

3)初始化

最后是初始化阶段(initialization),这一步真正去执行类初始化的代码逻辑,包括静态字段赋值的动作,以及执行类定义中的静态初始化块内的逻辑,编译器在编译阶段就会把这部分逻辑整理好,父类型的初始化逻辑优先于当前类型的逻辑。再来谈谈双亲委派模型,简单说就是当加载器(Class-Loader)试图加载某个类型的时候,除非父类加载器找不到相应类型,否则尽量将这个任务代理给当前加载器的父加载器去做。使用委派模型的目的是避免重复加载 Java 类型。

3,双亲委派模型

双亲委派模型工作过程是:

它首先不会自己去尝试加载这个类,而是把这个请求委派给父类加载器完成。每个类加载器都是如此,只有当父加载器在自己的搜索范围内找不到指定的类时(即ClassNotFoundException),子加载器才会尝试自己去加载。

为什么使用双亲委派模型

防止内存中出现多份同样的字节码

怎么打破双亲委派模型?

打破双亲委派机制则不仅要继承ClassLoader 类,还要重写loadClass 和findClass 方法。

4,内存泄漏和内存溢出?

内存泄漏(memoryleak),是指应用程序在申请内存后,无法释放已经申请的内存空间,一 次内存泄漏危害可以忽略,但如果任其发展最终会导致内存溢出(outofmemory)。如读取文件后流要进行及时的关闭以及对数据库连接的释放。
内存溢出(outofmemory)是指应用程序在申请内存时,没有足够的内存空间供其使用。如我们在项目中对于大批量数据的导入,采用分批量提交的方式。

5,常见的垃圾回收(GC)算法有哪些?简述其原理?

GC 最基础的算法有三种: 标记 -清除算法复制算法、标记-压缩算法,我们常用的垃圾回收器一般都采用分代收集算法
标记-清除算法,“标记-清除”(Mark-Sweep)算法,如它的名字一样,算法分为“标记”和“清除”两个阶段:首先标记出所有需要回收的对象,在标记完成后统一回收掉所有被标记的对象。
复制算法,“复制”(Copying)的收集算法,它将可用内存按容量划分为大小相等的两块,每次只使用其中的一块。当这一块的内存用完了,就将还存活着的对象复制到另外一块上面, 然后再把已使用过的内存空间一次清理掉。
标记-压缩算法,标记过程仍然与“标记-清除”算法一样,但后续步骤不是直接对可回收对象进行清理,而是让所有存活的对象都向一端移动,然后直接清理掉端边界以外的内存
分代收集算法,“分代收集”(Generational Collection)算法,把Java 堆分为新生代和老年代,这样就可以根据各个年代的特点采用最适当的收集算法。

6,JAVA的四种引用

1.强引用:

如果一个对象具有强引用,它就不会被垃圾回收器回收。即使当前内存空间不足,JVM也不会回收它,而是抛出 OutOfMemoryError 错误,使程序异常终止。如果想中断强引用和某个对象之间的关联,可以显式地将引用赋值为null,这样一来的话,JVM在合适的时间就会回收该对象

2.软引用:

在使用软引用时,如果内存的空间足够,软引用就能继续被使用,而不会被垃圾回收器回收,只有在内存不足时,软引用才会被垃圾回收器回收。

3.弱引用:

具有弱引用的对象拥有的生命周期更短暂。因为当 JVM 进行垃圾回收,一旦发现弱引用对象,无论当前内存空间是否充足,都会将弱引用回收。不过由于垃圾回收器是一个优先级较低的线程,所以并不一定能迅速发现弱引用对象

4.虚引用:

顾名思义,就是形同虚设,如果一个对象仅持有虚引用,那么它相当于没有引用,在任何时候都可能被垃圾回收器回收。

7,GC如何判断对象是否被回收

1.引用计数法:

每个对象有一个引用计数属性,新增一个引用的时候计数加1,引用释放时计数减1,当计数为0的时候就可以回收

2.可达性分析法:(GC ROOTS根节点算法)

从 GC Roots 开始向下搜索,搜索所走过的路径称为引用链。当一个对象到 GCRoots 没有任何引用链相连时,则证明此对象是不可用的,那么虚拟机就判断是可回收对象。
但是这样会存在一个问题:
引用计数法,可能会出现A 引用了 B,B 又引用了 A,这时候就算他们都不再使用了,但因为相互
引用 计数器=1 ,那这两个对象就都永远无法被回收。
解决:
只靠强引用计数方式,会存在循环引用的问题,导致对象永远无法被释放,弱引用就是专门用来解决循环引用问题的:
若 A 强引用了 B,那 B 引用 A 时就需使用弱引用,当判断是否为无用对象时仅考虑强引用计数是否为 0,不关心弱引用计数的数量
这样就解决了循环引用导致对象无法释放的问题,但这会引发野指针问题:当 B 要通过弱指针访问 A 时,A 可能已经被销毁了,那指向 A 的这个弱指针就变成野指针了。在这种情况下,就表示 A 确实已经不存在了,需要进行重新创建等其他操作

3.GC Roots解释 GC Roots的对象有:

虚拟机栈(栈帧中的本地变量表)中引用的对象
方法区中类静态属性引用的对象
方法区中常量引用的对象
本地方法栈中JNI(即一般说的Native方法)引用的对象
可达性算法中的不可达对象并不是立即死亡的,对象拥有一次自我拯救的机会。对象被系统宣告死亡至
少要经历两次标记过程:第一次是经过可达性分析发现没有与GC Roots相连接的引用链,第二次是在由
虚拟机自动建立的Finalizer队列中判断是否需要执行finalize()方法。
当对象变成(GC Roots)不可达时,GC会判断该对象是否覆盖了finalize方法,若未覆盖,则直接将其回
收。否则,若对象未执行过finalize方法,将其放入F-Queue队列,由一低优先级线程执行该队列中对象
的finalize方法。执行finalize方法完毕后,GC会再次判断该对象是否可达,若不可达,则进行回收,否
则,对象“复活”

8,ArrayList的扩容原理

当我们调用无参构造时,初始化一个初始值为空的数组,长度为0。
第一次添加元素初始值更新为10
当长度不够,扩容为当前容量的1.5倍

9,ArrayList的数据结构

底层实现是一个Object数组,有下标,有序,可以为null,可以重复,线程不安全,
查询修改快,因为有下标,增删慢,因为需要移动元素。

10,HashMap的头插法和尾插法

JDK1.7用的是头插法,而JDK1.8及之后使用的都是尾插法,那么他们为什么要这样做呢?因为JDK1.7是用单链表进行的纵向延伸,当采用头插法时会容易出现逆序且环形链表死循环问题。但是在JDK1.8之后是因为加入了红黑树使用尾插法,能够避免出现逆序且链表死循环的问题

11,HashMap的底层结构

JDK7:数组+链表 JDK8:数组+单项链表+红黑树
如果单向链表中的元素超过8个,单向链表这个数据结构会变成红黑树数据结构,当红黑树上的节点数量小于6时,会重新把红黑树变成单向链表数据结构

12 ,HashMap的扩容原理

默认初始容量16,默认加载因子0.75,扩容2倍与HashSet一样

13, 线程的创建方式?

方式一:继承Thread 类
方式二:实现Runnable 接口
方式三:实现Callable 接口
方式四:使用线程池的方式

14,线程的几种状态?(生命周期)


1、新建状态(New):

新创建了一个线程对象。

2、就绪状态(Runnable):

线程对象创建后,其他线程调用了该对象的start()方法。该状态的线程位于“可运行线程池”中,变得可运行,只等待获取CPU的使用权。即在就绪状态的进程除CPU 之外,其它的运行所需资源都已全部获得。

3、运行状态(Running):

就绪状态的线程获取了CPU,执行程序代码。

4、阻塞状态(Blocked):

阻塞状态是线程因为某种原因放弃CPU 使用权,暂时停止运行。直到线程进入就绪状态,才有机会转到运行状态。
阻塞的情况分三种:
(1) 、等待阻塞:运行的线程执行wait()方法,该线程会释放占用的所有资源,JVM 会把该线程放入“等待池”中。进入这个状态后,是不能自动唤醒的,必须依靠其他线程调用notify() 或notifyAll()方法才能被唤醒,
(2) 、同步阻塞:运行的线程在获取对象的同步锁时,若该同步锁被别的线程占用,则JVM
会把该线程放入“锁池”中。
(3) 、其他阻塞:运行的线程执行sleep()或join()方法,或者发出了I/O 请求时,JVM 会把该线程置为阻塞状态。当sleep()状态超时、join()等待线程终止或者超时、或者I/O 处理完毕时, 线程重新转入就绪状态。

5、死亡状态(Dead):

线程执行完了或者因异常退出了run()方法,该线程结束生命周期。

15,sleep和wait 的区别?

sleep 是线程类(Thread)的方法,导致此线程暂停执行指定时间, 把执行机会给其他线程,但是监控状态依然保持,到时后会自动恢复。调用sleep 不会释放对象锁。

wait 是Object 类的方法,对此对象调用wait 方法导致本线程放弃对象锁,进入等待此对象的等待锁定池,只有针对此对象发出notify 方法(或notifyAll)后本线程才进入对象锁定池准备获得对象锁进入运行状态。

16,产生死锁的条件?

1、互斥条件:一个资源每次只能被一个进程使用。
2、请求与保持条件:一个进程因请求资源而阻塞时 ,对已获得的资源保持不放。
3、不剥夺条件:进程已获得的资源,在末使用完之前,不能强行剥夺。
4、循环等待条件:若干进程之间形成一种头尾相接的循环等待资源关系。

17,谈谈对死锁的理解

两个线程使用两个资源,但同时两个线程又等待彼此释放锁

  1. /*
  2. 死锁代码要会写。
  3. 一般面试官要求你会写。
  4. 只有会写的,才会在以后的开发中注意这个事儿。
  5. 因为死锁很难调试。
  6. */
  7. public class DeadLock {
  8. public static void main(String[] args) {
  9. Object o1 = new Object();
  10. Object o2 = new Object();
  11. // t1和t2两个线程共享o1,o2
  12. Thread t1 = new MyThread1(o1,o2);
  13. Thread t2 = new MyThread2(o1,o2);
  14. t1.start();
  15. t2.start();
  16. }
  17. }
  18. class MyThread1 extends Thread{
  19. Object o1;
  20. Object o2;
  21. public MyThread1(Object o1,Object o2){
  22. this.o1 = o1;
  23. this.o2 = o2;
  24. }
  25. public void run(){
  26. synchronized (o1){
  27. try {
  28. Thread.sleep(1000);//锁住后睡一下,一定出问题
  29. } catch (InterruptedException e) {
  30. e.printStackTrace();
  31. }
  32. synchronized (o2){
  33. }
  34. }
  35. }
  36. }
  37. class MyThread2 extends Thread {
  38. Object o1;
  39. Object o2;
  40. public MyThread2(Object o1,Object o2){
  41. this.o1 = o1;
  42. this.o2 = o2;
  43. }
  44. public void run(){
  45. synchronized (o2){
  46. try {
  47. Thread.sleep(1000);//锁住后睡一下,一定出问题
  48. } catch (InterruptedException e) {
  49. e.printStackTrace();
  50. }
  51. synchronized (o1){
  52. }
  53. }
  54. }
  55. }

18,线程池的七个参数

1,corePoolSize

  1. 核心线程数量

2,maximumPoolSize

线程池允许创建的最大线程数

3,keepAliveTime

线程池空闲状态可以等待的时间

4,unit

        保活时间单位

5,workQueue

任务队列

6,threadFactory

用于设置创建线程的工厂

7,handler

    拒绝策略

19,拒绝策略(handler)

当提交的任务大于线程池的最大线程数+任务队列容量,就会触发拒绝策略。
AbortPolicy 丢弃任务并抛出RejectedExecutionException异常,默认方式
DiscardPolicy 丢弃任务,但不抛出异常,不推荐。
DiscardOldesPolicy 丢弃队列最前面的任务,执行后面的任务
CallerRunsPolicy 调用任务的main方法绕过线程池直接执行。由调用线程处理该任务

20,线程池的五种状态

线程池的5种状态:Running、ShutDown、Stop、Tidying、Terminated。

runing:

线程池处于运行状态,可以接受任务,执行任务,创建线程默认就是这个状态。

showDown

调用showdown()函数,不会接受新任务,但是会慢慢处理完堆积的任务。

Stop

调用showdownnow()M函数,不会接受新任务,不处理已有任务,会中断现有任务。

Tiding

当线程池状态为showdown或者stop,任务数量为0,就会变为tidying。这个时候会调用钩子函数terminated()。

TERMINATED

当钩子函数terminated()被执行完成之后,线程池彻底终止,就变成TERMINATED状态。

21,线程池的三种阻塞队列:

SynchronousQueue:同步移交队列
DelayedWorkQueue:无界队列
LinkedBlockingQueue:有界队列
三种阻塞队列:
BlockingQueue workQueue = null;
workQueue = new ArrayBlockingQueue<>(5);//基于数组的先进先出队列,有界workQueue = new LinkedBlockingQueue<>();//基于链表的先进先出队列,无界workQueue = new SynchronousQueue<>();//无缓冲的等待队列,无界
1, 有界队列
workQueue = new ArrayBlockingQueue<>(5);//基于数组的先进先出(FIFO)队列,支持公平锁和非公平锁,有界
workQueue = new LinkedBlockingQueue<>();//基于链表的先进先出(FIFO)队列,默认长度为 Integer.MaxValue 有OOM危险,有界
workQueue = new LinkedBlockingDeque(); //一个由链表结构组成的,双向阻塞队列,有界
2, 无界队列
workQueue = new PriorityBlockingQueue(); //支持优先级排序的无限队列,默认自然排序,可以实现 compareTo()方法指定排序规则,不能保证同优先级元素的顺序,无界。
workQueue = new DelayQueue(); //一个使用优先级队列(PriorityQueue)实现的无界延时队列,在创建时可以指定多久才能从队列中获取当前元素。只有延时期满后才能从队列中获取元素。
workQueue = new LinkedTransferQueue(); //一个由链表结构组成的,无界阻塞队列
3, 同步移交队列
workQueue = new SynchronousQueue<>();//无缓冲的等待队列,队列不存元素,每个put操作必须等待take操作,否则无法添加元素,支持公平非公平锁,无界


22, synchronized的三种用法和区别?

1.作用于实例方法,当前实例加锁,进入同步代码前要获得当前实例的锁;
2.作用于静态方法,当前类加锁,进去同步代码前要获得当前类对象的锁;
3.作用于代码块,这需要指定加锁的对象,对所给的指定对象加锁,进入同步代码前要获得指定对象的锁。

23,java锁

synchronized和lock的区别

典型回答
synchronized 是 Java 内建的同步机制,它提供了互斥的语义和可见性,当一个线程已经获取当前锁时,其他试图获取的线程只能等待或者阻塞在那里。
synchronized可以用来修饰方法,也可以使用在特定的代码块儿上,本质上 synchronized 方法等同于把方法全部语句用 synchronized 块包起来。

ReentrantLock,通常翻译为再入锁,是 Java 5 提供的锁实现,它的语义和 synchronized 基本相同。
再入锁通过代码直接调用 lock() 方法获取,代码书写也更加灵活。与此同时,ReentrantLock 提供了很多实用的方法,能够实现很多 synchronized 无法做到的细节控制,比如可以控制 fairness,也就是公平性,或者利用定义条件等。
但是,编码中也需要注意,必须要明确调用 unlock() 方法释放,不然就会一直持有该锁。synchronized 和 ReentrantLock 的性能不能一概而论,早期版本 synchronized 在很多场景下性能相差较大,在后续版本进行了较多改进,在低竞争场景中表现可能优于 ReentrantLock。

理解什么是线程安全。

线程安全是一个多线程环境下正确性的概念,也就是保证多线程环境下共享的、可修改的状态的正确性,这里的状态反映在程序中其实可以看作是数据。
换个角度来看,如果状态不是共享的,或者不是可修改的,也就不存在线程安全问题,进而可以推理出保证线程安全的两个办法:
封装:通过封装,我们可以将对象内部状态隐藏、保护起来。
不可变:还记得我们在专栏第 3 讲强调的 final 和 immutable 吗,就是这个道理,Java 语言目前还没有真正意义上的原生不可变,但是未来也许会引入。

>> 线程安全需要保证几个基本特性:

原子性,简单说就是相关操作不会中途被其他线程干扰,一般通过同步机制实现。
可见性,是一个线程修改了某个共享变量,其状态能够立即被其他线程知晓,通常被解释为将线程本地状态反映到主内存上,volatile 就是负责保证可见性的。
有序性,是保证线程内串行语义,避免指令重排等。

>synchronized.
00、ReentrantLock 等机制的基本使用与案例。
> 掌握 synchronized、ReentrantLock 底层实现
synchronized 代码块是由一对儿 monitorenter/monitorexit 指令实现的,Monitor 对象是同步的基本实现单元
在 Java 6 之前,Monitor 的实现完全是依靠操作系统内部的互斥锁,因为需要进行用户态到内核态的切换,所以同步操作是一个无差别的重量级操作。
现代的(Oracle)JDK 中,JVM 对此进行了大刀阔斧地改进,提供了三种不同的 Monitor 实现,也就是常说的三种不同的锁:偏斜锁(Biased Locking)、轻量级锁和重量级锁,大大改进了其性能。
其他lock
StampedLock,在提供类似读写锁的同时,还支持优化读模式。
优化读基于假设,大多数情况下读操作并不会和写操作冲突,其逻辑是先试着读,然后通过 validate 方法确认是否进入了写模式,
如果没有进入,就成功避免了开销;如果进入,则尝试获取读锁。

ReadWriteLock
虽然 ReentrantLock 和 synchronized 简单实用,但是行为上有一定局限性,通俗点说就是“太霸道”,要么不占,要么独占。
实际应用场景中,有的时候不需要大量竞争的写操作,而是以并发读取为主,如何进一步优化并发操作的粒度呢?
Java 并发包提供的读写锁等扩展了锁的能力,它所基于的原理是多个读操作是不需要互斥的,因为读操作并不会更改数据,所以不存在互相干扰。
而写操作则会导致并发一致性的问题,所以写线程之间、读写线程之间,需要精心设计的互斥逻辑。
在运行过程中,如果读锁试图锁定时,写锁是被某个线程持有,读锁将无法获得,而只好等待对方操作结束,这样就可以自动保证不会读取到有争议的数据。
读写锁看起来比 synchronized 的粒度似乎细一些,但在实际应用中,其表现也并不尽如人意,主要还是因为相对比较大的开销。

> 理解锁膨胀、降级;
所谓锁的升级、降级,就是 JVM 优化 synchronized 运行的机制,当 JVM 检测到不同的竞争状况时,会自动切换到适合的锁实现,这种切换就是锁的升级、降级。
1: 当没有竞争出现时,默认会使用偏斜锁。JVM 会利用 CAS 操作(compare and swap),在对象头上的 Mark Word 部分设置线程 ID,以表示这个对象偏向于当前线程,所以并不涉及真正的互斥锁。这样做的假设是基于在很多应用场景中,大部分对象生命周期中最多会被一个线程锁定,使用偏斜锁可以降低无竞争开销。
2: 如果有另外的线程试图锁定某个已经被偏斜过的对象,JVM 就需要撤销(revoke)偏斜锁,并切换到轻量级锁实现。轻量级锁依赖 CAS 操作 Mark Word 来试图获取锁,如果重试成功,就使用普通的轻量级锁;否则,进一步升级为重量级锁。
3: 当 JVM 进入安全点(SafePoint)的时候,会检查是否有闲置的 Monitor,然后试图进行降级。

>> 理解偏斜锁、自旋锁、轻量级锁、重量级锁等概念。
>> 偏向锁
大多数情况下锁不仅不存在多线程竞争,而且总是由同一线程多次获得。偏向锁的目的是在某个线程获得锁之后,消除这个线程锁重入(CAS)的开销,看起来让这个线程得到了偏护。另外,JVM对那种会有多线程加锁,但不存在锁竞争的情况也做了优化,听起来比较拗口,但在现实应用中确实是可能出现这种情况,因为线程之前除了互斥之外也可能发生同步关系,被同步的两个线程(一前一后)对共享对象锁的竞争很可能是没有冲突的。对这种情况,JVM用一个epoch表示一个偏向锁的时间戳(真实地生成一个时间戳代价还是蛮大的,因此这里应当理解为一种类似时间戳的identifier)
1
>>> 偏向锁的获取
当一个线程访问同步块并获取锁时,会在对象头和栈帧中的锁记录里存储锁偏向的线程ID,以后该线程在进入和退出同步块时不需要花费CAS操作来加锁和解锁,而只需简单的测试一下对象头的Mark Word里是否存储着指向当前线程的偏向锁,如果测试成功,表示线程已经获得了锁,如果测试失败,则需要再测试下Mark Word中偏向锁的标识是否设置成1(表示当前是偏向锁),如果没有设置,则使用CAS竞争锁,如果设置了,则尝试使用CAS将对象头的偏向锁指向当前线程。
1
>>> 偏向锁的撤销
偏向锁使用了一种等到竞争出现才释放锁的机制,所以当其他线程尝试竞争偏向锁时,持有偏向锁的线程才会释放锁。偏向锁的撤销,需要等待全局安全点(在这个时间点上没有字节码正在执行),它会首先暂停拥有偏向锁的线程,然后检查持有偏向锁的线程是否活着,如果线程不处于活动状态,则将对象头设置成无锁状态,如果线程仍然活着,拥有偏向锁的栈会被执行,遍历偏向对象的锁记录,栈中的锁记录和对象头的Mark Word,要么重新偏向于其他线程,要么恢复到无锁或者标记对象不适合作为偏向锁,最后唤醒暂停的线程。
1
>>> 偏向锁的设置
关闭偏向锁:偏向锁在Java 6和Java 7里是默认启用的,但是它在应用程序启动几秒钟之后才激活,如有必要可以使用JVM参数来关闭延迟-XX:BiasedLockingStartupDelay = 0。如果你确定自己应用程序里所有的锁通常情况下处于竞争状态,可以通过JVM参数关闭偏向锁-XX:-UseBiasedLocking=false,那么默认会进入轻量级锁状态。
1
>> 自旋锁
线程的阻塞和唤醒需要CPU从用户态转为核心态,频繁的阻塞和唤醒对CPU来说是一件负担很重的工作。同时我们可以发现,很多对象锁的锁定状态只会持续很短的一段时间,例如整数的自加操作,在很短的时间内阻塞并唤醒线程显然不值得,为此引入了自旋锁。

所谓“自旋”,就是让线程去执行一个无意义的循环,循环结束后再去重新竞争锁,如果竞争不到继续循环,循环过程中线程会一直处于running状态,但是基于JVM的线程调度,会出让时间片,所以其他线程依旧有申请锁和释放锁的机会。

自旋锁省去了阻塞锁的时间空间(队列的维护等)开销,但是长时间自旋就变成了“忙式等待”,忙式等待显然还不如阻塞锁。所以自旋的次数一般控制在一个范围内,例如10,100等,在超出这个范围后,自旋锁会升级为阻塞锁。
> 轻量级锁
加锁
线程在执行同步块之前,JVM会先在当前线程的栈桢中创建用于存储锁记录的空间,并将对象头中的Mark Word复制到锁记录中,官方称为Displaced Mark Word。然后线程尝试使用CAS将对象头中的Mark Word替换为指向锁记录的指针。如果成功,当前线程获得锁,如果失败,则自旋获取锁,当自旋获取锁仍然失败时,表示存在其他线程竞争锁(两条或两条以上的线程竞争同一个锁),则轻量级锁会膨胀成重量级锁。
1解锁
轻量级解锁时,会使用原子的CAS操作来将Displaced Mark Word替换回到对象头,如果成功,则表示同步过程已完成。如果失败,表示有其他线程尝试过获取该锁,则要在释放锁的同时唤醒被挂起的线程。
1重量级锁
重量锁在JVM中又叫对象监视器(Monitor),它很像C中的Mutex,除了具备Mutex(0|1)互斥的功能,它还负责实现了Semaphore(信号量)的功能,也就是说它至少包含一个竞争锁的队列,和一个信号阻塞队列(wait队列),前者负责做互斥,后一个用于做线程同步。
原文链接:https://blog.csdn.net/handong106324/article/details/105444858

24,synchronized实现原理?

同步代码块是通过 monitorenter 和 monitorexit 指令获取线程的执行权
同步方法通过加 ACC_SYNCHRONIZED 标识实现线程的执行权的控制


25,CAS和ABA?

CAS(Compare-and-Swap),即比较并替换,是一种实现并发算法时常用到的技术,Java并发包中的很多类都使用了CAS技术。


26,对象的锁升级过程?

锁升级的方向是:无锁——>偏向锁——>轻量级锁——>重量级锁,并且膨胀方向不可逆。
1.偏向锁
偏向锁是JDK6中引入的一项锁优化,大多数情况下,锁不仅不存在多线程竞争,而且总是由同一线程多次获得,为了让线程获得锁的代价更低而引入了偏向锁。
偏向锁会偏向于第一个获得它的线程,如果在接下来的执行过程中,该锁没有被其他的线程获取,则持有偏向锁的线程将永远不需要同步。
2.轻量级锁
如果明显存在其它线程申请锁,那么偏向锁将很快升级为轻量级锁。
3.自旋锁
自旋锁原理非常简单,如果持有锁的线程能在很短时间内释放锁资源,那么那些等待竞争锁的线程就不需要做内核态和用户态之间的切换进入阻塞挂起状态,它们只需要等一等(自旋),等持有锁的线程释放锁后即可立即获取锁,这样就避免用户线程和内核的切换的消耗。
4.重量级锁
指的是原始的Synchronized的实现,重量级锁的特点:其他线程试图获取锁时,都会被阻塞,只有持有锁的线程释放锁之后才会唤醒这些线程。
原文链接:https://blog.csdn.net/qq9808/article/details/104867285

27,mysql中innodb和myisam的区别?

MyISAM
● 不需要事务支持(不支持)
● 并发相对较低(锁定机制问题)
● 数据修改相对较少(阻塞问题),以读为主
● 数据一致性要求不是非常高
InnoDB
● 需要事务支持(具有较好的事务特性)
● 行级锁定对高并发有很好的适应能力,但需要确保查询是通过索引完成
● 数据更新较为频繁的场景
● 数据一致性要求较高
● 硬件设备内存较大,可以利用InnoDB较好的缓存能力来提高内存利用率,尽可能减少磁盘 IO,

28,SQL语句分类?

1.数据查询语言(DQL)

数据查询语言(Data Query Language, DQL)是SQL语言中,负责进行数据查询而不会对数据本身进行修改的语句,

2.数据定义语言(DDL)

数据定义语言 (Data Definition Language, DDL) 是SQL语言集中,负责数据结构定义与数据库对象定义的语言,由CREATE、ALTER与DROP三个语法所组成。

3.数据操纵语言(DML)

数据操纵语言(Data Manipulation Language, DML)负责对数据库对象运行数据访问工作的指令集,以INSERT、UPDATE、DELETE三种指令为核心,分别代表插入、更新与删除。

4.数据控制语言(DCL)

数据控制语言 (Data Control Language) 是一种可对数据访问权进行控制的指令,它可以控制特定用户账户对数据表、查看表、预存程序、用户自定义函数等数据库对象的控制权。DCL以控制用户的访问权限为主,GRANT为授权语句,对应的REVOKE是撤销授权语句。

5.指针控制语言(CCL)

用于对一个或多个表单独行的操作。

6.事务处理语言(TPL)

它的语句能确保被DML语句影响的表的所有行及时得以更新。TPL语句包括BEGIN TRANSACTION,COMMIT和ROLLBACK。事务、提交、回滚,

29,事务的ACID特性?

原子性:

说明事务是最小的工作单元,不可再分
一个事务是一个整体 不可拆分 要么都执行 要么都不执行**

一致性:

事务执行之前和事务之后必须保证数据(总体的数据)的一致**

隔离性:

事务与事务之间是相互隔离的 不能交叉访问,相互之间无任何关系**

持久性:

经过事务处理之后的数据 将永久发生改变 数据持久化**

30,脏读、幻读、不可重复读?

脏读:一个事务读取到另一个事务中未提交的数据
不可重复读:在我们的一个事务中,读取到的数据是不一样的
幻读:一个事务中读取的数据的数量不一致,

31,内连接、左外、右外、子查询的意义和差异?

内连接:取两个表相同的值作为条件
左连:以左边的表作为标准,其他表没有值的话,null补充
右连:以右边的表作为标准,其他表没有值的话,null补充
子查询:查询后的数据可以嵌套另一个查询

32,jdbc连接数据库的步骤

    1、注册驱动<br />        2、创建连接<br />        3、创建sql对象<br />        4、执行sql语句<br />        5、处理结果<br />        6、释放资源

33,Mybatis多参数处理方案?

1. 利用参数出现的顺序(序号参数绑定)

利用mapper.xml配置

<select id="MutiParameter" resultType="com.jt.mybatis.entity.User">
        select * from user where id = #{param1} and username = #{param2}
</select>

利用mybatis注解方式

@Select("select * from user where id = #{arg0} and username = #{arg1}")
User MutiParameter(int id,String username);

2.使用注解需要使用到mybatis @Param注解(注解参数绑定)

利用mapper.xml配置

<select id="MutiParameter" resultType="com.jt.mybatis.entity.User">
        select * from user where id = #{id} and username = #{username}
</select>

利用mybatis注解方式

@Select("select * from user where id = #{id} and username = #{username}")
User MutiParameter(@Param("id")int id,@Param("username")String username);

3.使用map 需要map的键和#{内容}里面的内容一致 (Map参数绑定)

利用mapper.xml配置

<select id="MutiParameter" resultType="com.jt.mybatis.entity.User">
        select * from user where id = #{id} and username = #{username}
</select>

利用mybatis注解方式

@Select("select * from user where id = #{id} and username = #{username}")
User MutiParameter(Map<String, Object> params);

4.把参数封装在Javabean中(对象参数绑定)

利用mapper.xml配置

<select id="MutiParameter" resultType="com.jt.mybatis.entity.User">
        select * from user where id = #{id} and username = #                                                                                                                                 {username}
</select>

利用mybatis注解方式

@Select("select * from user where id = #{id} and username = #{username}")
User MutiParameter(User user);

34,Mybatis的动态SQL和实现批处理?

MyBatis的映射文件中支持在基础SQL上添加一些逻辑操作,并动态拼接完整的SQL之后再执行,以达到SQL复用、简化编程的效果。

#### 35.1 sql

```sql



SELECT id,name,author,publish,sout

<select id="selectBookByCondition" resultType="com.qf.mybatis.part2.dynamiic.Book"><br />        <!--  通过ID引用SQL片段  --><br />        <include refid="BOOKS_FIELD" /><br />        FROM t_books<br />    </select><br /></mapper><br />```

#### 35.2 where

```sql

```

#### 35.3 set

sql<br /><update id="upsateBookByCondition"><br /> UPDATE t_books<br /> <!-- set子句中满足条件的if,会自动忽略后缀 (如: , ) --><br /> <set> <br /> <if test="name!=null"><br /> name =#{name} ,<br /> </if><br /> <br /> <if test="author!=null"><br /> author=#{author} ,<br /> </if><br /> <br /> <if test="publish!=null"><br /> publish = #{publish} ,<br /> </if><br /> <br /> <if test="sort!=null"><br /> sort = #{sort} ,<br /> </if><br /> </set><br /> WHERE id = #{id}<br /></update><br />

#### 35.4 trim

代替:

sql<br /><select id="selectBookByCondition" resultType="com.qf.mybatis.day2.dynamic.Book"><br /> SELECT id,name,author,publish,sort<br /> FROM t_books<br /> <!-- 增加WHERE前缀,自动忽略前缀 --><br /> <trim prefix="WHERE" prefixOverrides="AND|OR"><br /> <if test="id!=null"><br /> and id =#{id}<br /> </if><br /> <br /> <if test="name!=null"><br /> and name =#{name}<br /> </if><br /> <br /> <if test="author!=null"><br /> and author=#{author}<br /> </if><br /> <br /> <if test="publish!=null"><br /> and publish = #{publish}<br /> </if><br /> <br /> <if test="sort!=null"><br /> and sort = #{sort}<br /> </if><br /> </trim><br /></select><br />

sql<br /><update id="updateBookByCondition"><br /> UPDATE t_books<br /> <!-- 增加SET前缀,自动忽略后缀 --><br /> <trim prefix="SET" suffixOverrides=","><br /> <if test="name!=null"><br /> name =#{name} ,<br /> </if><br /> <br /> <if test="author!=null"><br /> author=#{author} ,<br /> </if><br /> <br /> <if test="publish!=null"><br /> publish = #{publish} ,<br /> </if><br /> <br /> <if test="sort!=null"><br /> sort = #{sort} ,<br /> </if><br /> </trim><br /> WHERE id = #{id}<br /></update><br />

sql<br /><delete id="deleteBookByIds"><br /> DELETE FROM t_books<br /> WHERE id in<br /> <foreach collection="list" open="(" separator="," close=")" item="id" index="i"><br /> #{id}<br /> </foreach><br /></delete><br />

| 参数 | 描述 | 取值 |
| ————— | ———— | —————————————————————- |
| collection | 容器类型 | list、array、map |
| open | 起始符 | ( |
| close | 结束符 | ) |
| separator | 分隔符 | , |
| index | 下标符 | 从0开始,依次递增 |
| item | 当前项 | 任意名称(循环中通过#{任意名称}表达式访问) |

35,MyBatis的mapper中#{}和${}的区别是什么?

    #{}是预编译处理,相当于我们以前jdbc中的?,<br />        ${}是字符串替换,也就是字符串拼接。<br />        Mybatis在处理#{}时,会将sql中的#{}替换为?号,调用PreparedStatement的set方法来赋值;<br />        Mybatis在处理${}时,就是把${}替换成变量的值。<br />        使用#{}可以有效的防止SQL注入,提高系统安全性。    

36, Mybatis的缓存策略和懒加载?

一级缓存是SQLSession级别缓存,默认是开启的,所以在同一个SQLSession对象操作时,对同一个SQL来执行时,只需要查询一次数据库。
二级缓存是在mapper层面上,多个SQLSession去操作同一个mapper对象的SQL语句,多个SQLSession是可以共用一个二级缓存,二级缓存是跨SQLSession的。二级缓存是需要开启的
懒加载也就是延迟加载。

37,IOC和DI区别?

IoC:控制反转,指将对象的创建权,反转到spring容器。
DI:依赖注入,指在创建对象的过程中,将对象依赖的属性通过配置进行注入。DI的实现依赖于IoC,先有控制反转才有依赖注入

38,Spring的AOP的理解

1)什么是AOP?

面向切面编程。利用它可以对业务逻辑的各个部分进行隔离,从而使得业务部分之间的耦合度降低,提高程序开发效率。
应用场景有:日志记录、性能统计、安全控制、事务处理、异常处理…
通俗描述:不通过修改源代码方式,在主干功能里添加新功能


39,Spring循环依赖的解决

https://blog.csdn.net/wangxuelei036/article/details/104960558?ops_request_misc=%257B%2522request%255Fid%2522%253A%2522163477488816780261927472%2522%252C%2522scm%2522%253A%252220140713.130102334.pc%255Fall.%2522%257D&request_id=163477488816780261927472&biz_id=0&utm_medium=distribute.pc_search_result.none-task-blog-2~all~first_rank_ecpm_v1~rank_v31_ecpm-6-104960558.pc_search_result_cache&utm_term=Spring%E7%9A%84%E5%BE%AA%E7%8E%AF%E4%BE%9D%E8%B5%96%E7%9A%84%E8%A7%A3%E5%86%B3%EF%BC%9F&spm=1018.2226.3001.4187

40,vue.js的三种绑定关系?

1,属性绑定

Mustache 语法不能在 HTML attribute 中使用,然而,可以使用 v-bind指令
格式: v-bind:属性名=”Vue中data的属性名”
v-bind:属性名 简称 :属性名

2,事件绑定

使用 v-on 指令 (通常缩写为 @ 符号) 来监听 DOM 事件,并在触发事件时执行一些JavaScript
常用事件: click dblclick change keyup
在监听键盘事件时,Vue 允许为 v-on 或者 @ 在监听键盘事件时添加按键修饰符
事件处理程序中可以有多个方法,这些方法由逗号运算符分隔

3,双向绑定

用 v-model 指令在表单