1、集合和数据结构
- Collection和Collections的区别?
Collection
是集合类的上级接口,继承他的接口主要有Set和List。
Collections
是针对集合的一个工具类,它提供一系列静方法实现对各种集合的搜索、排序、线程安全化等操作。
- List和Map的区别?
List
是存储单列数据的集合,存储的数据是有顺序的,并且允许重复。
Map
是存储键和值这样的双列数据的集合,存储的数据没有顺序,其中键不能重复,但是值可以重复。
- ArrayList,Vector,LinkedList的存储性能和特性?
ArrayList
和Vector
都是使用数组方式存储数据,此数组元素数大于实际存储的数据以便增加和插入元素,它们都允许直接按序号索引元素,但是插入元素要涉及元素移动等内存操作,所以索引数据快而插入数据慢。Vector
由于使用了synchronized
方法(线程安全),通常性能上比ArrayList
差,而LinkedList
使用双向链表实现存储,按序号索引数据需要进行前向或后向遍历,但是插入数据时只需记录本项的前后项即可,所以查询慢插入快。
简述:
ArrayList | Vector | LinkedList |
---|---|---|
用数组实现,查找快,增删改慢 | 同ArrayList,但是线程安全的,效率比ArrayList慢 | 用链表结构实现,增删改速度快,但是查询慢 |
- HashMap和Hashtable的区别?
HashMap | Hashtable | |
相同点 | 都实现了Map接口,都是KEY-VALUE存储方式,所用算法差不多 | |
区别1 | 非线程安全 | 线程安全 |
区别2 | 默认初始化大小16,每次扩容为原来的2倍 | 默认初始化大小11,每次扩容为原来的2n+1 |
区别3 | 方法不同步,效率高一些 | 方法同步,效率低一些 |
区别4 | 允许将null作为一个entry的key或者value | 不允许将null作为一个entry的key或者value |
- Set里的元素不能重复,用什么方法区分重复与否?==和equals()的区别?
使用equals()
equals()
是超类Object中的方法,用来检测两个对象的内容是否相等。
==
比较基本类型时比较的是值是否相等,比较引用类型时比较的是引用地址是否相同。
- 数组有没有length()这个方法?String有没有?
String有length()这个方法,数组有length这个属性。
- 常用数据结构?
链表、堆栈、二叉树、队列、图、堆、集合……
2、面向对象和底层原理
- 面向对象的特征?
封装
特性是由类来体现的,将现实生活中的一类实体定义成类,包括属性和行为(在Java中就是方法),在行为中实现一定的功能,也可操作属性。
抽象
是将一类实体的共同特性抽象出来,封装在一个抽象类中,因为抽象实体并不是一个真正的对象,它的属性还不能完全描述一个对象,所以不能实例化。
继承
可以使得子类具有父类的属性和方法或者重新定义、追加属性和方法等。
多态
是通过传递给父类对象引用不同的子类从而表现出不同的行为。
- Overload和Override的区别?
Override
重写是父类与子类之间的多态性的一种表现(方法名相同,参数类型、个数相同,返回类型也要相同)。
Overload
重载是一个类中多态性的一种表现(方法名相同,参数类型、个数不同,返回值无关)。
- 构造器Constructor是否能被重写?
构造器Constructor不能被继承,因此不能重写,但可以被重载。
- 抽象类和接口有什么区别?
抽象类:
1)仅仅是声明方法,没有方法体的类。
2)包含抽象方法的类一定是抽象类。抽象类可以包含完整的方法,也可以包含抽象方法,所以抽象类可以没有抽象方法。
3)抽象类是给子类继承的,不能实例化。
接口:
1)特殊的抽象类,所有的方法都是抽象方法,不能包含完整的方法。
2)接口是给子类继承的或实现的,不能实例化。
3)接口可以多继承。
- 接口和内部类、抽象类的特征?
接口 | 内部类 | 抽象类 |
---|---|---|
在一个类里,只有申明没有实现 | 是在一个类的内部定义的一个类 | 是以abstract 定义的,里面至少有一个抽象方法 |
- 接口是否可以继承接口?抽象类是否可以实现接口?抽象类是否可以继承实体类?
- 接口可以继承接口。
- 抽象类可以实现接口。
- 抽象类是否可以继承实体类的前提是实体类必须有明确的构造函数。
- Java中实现多态的机制是什么?
方法的重写和重载是Java多态性的不同表现。重写(Overriding)是父类与子类之间多态性的一种表现,重载(Overloading)是一个类中多态性的一种表现。
- 静态变量和实例变量的区别?
1)静态变量是类加载时创建,实例变量是创建对象(new)时创建。
2)静态变量是程序退出时释放内存,实例变量是对象不存在时释放内存。
3)静态变量是所有对象共享的,是属于类的,实例变量是每个对象独有的。
4)静态变量可以通过类名调用,实例变量只能通过对象名调用。
- abstract的method是否可以同时是static,是否可同时是native,是否可同时是synchronized?
都不能。
还不能和修饰符final
、private
一起使用。
- 什么是内部类?用内部类有什么好处?
定义在一个类内部的类被称为内部类。
1、内部类对象能访问其所处类的私有属性和方法。
2、内部类能隐藏起来,不能被同包中的其他类访问。
3、匿名内部类可以方便地用在回调方法中。
- 写clone()方法时,通常要写地一段代码?
重写
clone()
方法的时候,需要先克隆父类,所以一般要写super.clone()
。
使用clone
的时候,有浅克隆和深克隆之分,浅克隆出来的对象的引用类型的变量存储的还是原对象的值,修改时可能会修改到原对象引用的对象。
- 对象的浅克隆和深克隆的区别?
浅克隆:
对当前对象进行克隆,并克隆该对象所包含的8种基本数据类型和String类型数据(拷贝一份该对象并重新分配内存,即产生了新对象);但如果被克隆的对象中包含除8种基本数据类型和String类型外的其他类型的属性,浅克隆不会克隆这些属性(即不会为这些属性分配内存,而是引用原对象中的属性)。
深克隆:
深拷贝会完全复制整个对象,包括这个对象所包含的内部对象。
- 两个对象值相同(x.equals(y)==true),但却可以有不同的hash code,这句话对不对?
不对,hash code相同。
- 两个对象的hashCode()相同,那么equals()一定为true吗?
不一定,例如在散列表中,
hashCode()
相等即两个键值对的哈希值相等,然而哈希值相等并不能得出键值对相等。
3、内存
- Java创建对象的方式?
new创建新对象
通过反射机制
采用clone机制
通过反序列化机制
- 创建对象的过程?
1、判断对象对应的类是否被加载、链接、初始化。
2、为对象分配内存空间(如果内存堆完整—指针碰撞法,不完整—空闲列表法),是否完整是根据垃圾回收算法和垃圾收集器决定的。
3、处理非线程安全问题,解决方案:1)CAS失败重试保证原子性。
2)TLAB每一个线程预先分配一块区域。
4、初始化分配到的空间。
5、设置对象头信息。
6、执行init方法初始化。
- Java对象结构(对象在内存中的存储布局)?
对象头:
第一部分存储对象自身的运行时数据:哈希码、GC分代年龄、锁标识状态等。第二部分是指针类型,指向对象的类元数据类型,若是数组对象,则对象头中还有一部分记录数组长度。
实例数据:
用来存储对象真正有效的信息(包括父类继承下来的和自己定义的)。
对齐填充:
JVM要求对象起始地址必须是8字节的整数倍。
- 对象怎么定位?
间接访问:
使用句柄访问,在java堆中划出一块内存作为句柄池。reference
中存储的就是对象的句柄地址,而句柄中包含对象实例数据与类型数据具体地址信息。
直接访问:
在java堆中对象的内存布局中必须考虑如何放置访问类型的相关信息,而reference
中直接存储的就是对象地址。
- 对象怎么分配?
对象优先分配在Eden区(新生代),如果空间不够时虚拟机执行一次Minor GC。
大对象直接进入老年代,避免Eden区和两个Survivor区(幸存区)之间发生大量的内存拷贝。
长期存活的对象进入老年代。虚拟机为每个对象定义了一个年龄计数器,对象经过一次Minor GC就会进入Survivor区,之后没经过一次Minor GC那么对象年龄加一,直到达到进入老年区的阀值。
动态判断对象的年龄。如果Survivor区中相同年龄的所有对象大小的总和大于Survivor空间的一半,年龄大于或等于该年龄的对象可以直接进入老年代。
空间分配担保。每次进行Minor GC时,JVM会计算Survivor区移至老年区的对象的平均大小,若这个值大于老年区的剩余值大小则进行一次Full Gc,若小于检查HandlePromotionFailure设置,若true则只进行Monitor GC,若false则进行Full GC。
- 为什么会有两个Survivor区?并且存活且年龄不够大的对象会从一个Survivor区转到另一个Survivor区?
根据根可达算法,JVM会寻找到所有正在使用的对象,没有使用的就是垃圾。通常来说,大部分对象都是用完就抛弃的,所以真正在Survivor区长时间存活的对象非常少,将这部分对象从一个Survivor区转到另一个Survivor区后,就可以直接对这个Survivor区进行全量的空间回收,效率会很高。
- Class对象是在堆还是方法区?
在JVM中,使用
OOP-KLASS
模型表示java对象
。
JVM加载
一个类的时候会创建一个instanceKlass
,用来表示这个类的元数据
,包括常量池、字段、方法等存放在方法区
。
在new一个对象
时,JVM会创建instanceOopDesc
,来表示这个对象
,存放在堆
区,其引用存放在栈区。
- Object o=new Object()在内存上占用多少字节?
- 在开启指针压缩的情况下,markword占用8字节,classpoint占用4字节,实例数据无数据,总共12字节,由于对象需要为8的整数倍,Padding会补充4字节,总共占用16字节。
- 在没有指针压缩的情况下,markword占用8字节,classpoint占用8字节,实例数据无数据,总共16字节。
- Java反射的原理
反射机制是在运行时,对于任意一个类都能够知道这个类的所有属性和方法;对于任意对象都能调用它的任意一个方法。在java中,只要给定类的名字,就能通过反射机制来获得类的所有信息。这种动态获取的信息以及动态调用对象的方法的功能成为Java语言的反射机制。
- 反射获取Class对象的4种方法?
- Class.forName(“类的路径”)
- 类名.class
- 对象名.getClass()
- 基本类型的包装类,可以调用包装类的Type属性来获得该包装类的Class对象
- 实现Java反射的类?
1)
Class:
表示正在运行的Java应用程序中的类和接口(所有获取对象的信息都需要Class类来实现)
2)Field:
提供有关类和接口的属性信息,以及对它的动态访问权限
3)Constructor:
提供关于类的单个构造方法的信息以及它的访问权限
4)Method:
提供类或接口中某个方法的信息
- 反射机制的优缺点?
优点:
1、能够运行时动态获取类的实例,提高灵活性
2、与动态编译结合
缺点:
使用反射性能较低,需要解析字节码,将内存中的对象进行解析
解决方案:
1、通过setAccessible(true)
关闭JDK的安全检查来提升反射速度
2、多次创建一个类的实例,有缓存会快很多
3、ReflectASM
工具类,通过字节码生成的方式加快反射速度
4、类加载过程
1、类的加载:将类的Class文件加载到内存中,并为其生成Java.lang.class对象(由类加载器完成)
2、类的链接:将Java类的二进制数据合并到jvm运行状态中
1)验证:确保类符合JVM规范,保证安全性
2)准备:为类变量分配内存和初始化值
3)解析:JVM常量池中的符号引用(常量名)直接替换成直接引用地址
3、类的初始化:JVM对类进行初始化
执行类构造器方法(这个方法是由编译期自动收集类中所有类变量的赋值动作和静态代码块中的语句合并产生的(该类构造器是构造类信息的,不是构造对象的构造器))
当初始化一个类的时候,如果发现其父类还没有进行初始化,则需要先触发其父类的初始化
JVM会保证一个类的方法在多线程环境中被正确加锁和同步
5、类加载器
引导类加载器、扩展类加载器、系统类加载器、自定义加载器(其中系统类加载器是自定义加载器的父类,通过继承ClassLoader实现)
作用:
将class文件字节码内容加载到内存中,并将这些静态数据转换成方法区运行时的数据,然后在堆中生成一个代表这个类的Java.lang.class对象,作为方法区中类数据的访问入口
6、类加载机制
双亲委派机制
当一个类加载器收到类加载请求的时候,它首先不会自己加载这个类的信息,而是把该请求转发给父类加载器,依次向上。所以所有的类加载请求都会被传递到父类加载器中,只有当父类加载器中无法加载到所需的类子类加载器才会自己尝试加载该类,若当前类加载器和所有父类加载器都无法加载该类时,抛出ClassNotFindException异常。
作用:
1)防止重复加载同一个.class
2)保证核心.class不被篡改,提高系统安全性
7、垃圾回收
- GC是什么?为什么要有GC?有什么办法主动通知虚拟机进行垃圾回收?并考虑2种回收机制?
GC是垃圾收集机制。垃圾回收可以有效地防止内存泄漏,有效地使用可以使用的内存。垃圾回收器通常是作为一个单独的低级别的线程运行,不可预知的情况下对内存堆中已经死亡的或者长时间没有使用的对象进行清楚和回收,程序员不能实时地调用垃圾回收器对某个对象或所有对象进行垃圾回收。但是可以手动执行
System.gc()
,通知GC运行,但是Java语言规范并不保证GC一定会立刻执行。回收机制分为代复制垃圾回收、标记垃圾回收和增量垃圾回收。
- Java中存在内存泄漏吗?
内存泄漏是指一个不再被程序使用的对象或变量一直被占据在内存中。即这个对象无用却无法被垃圾回收器回收,这就是Java中可能出现的内存泄漏情况。
- JVM的永久代中会发生垃圾回收吗?
垃圾回收不会发生在永久代,如果永久代满了或者超过了临界值,会触发完全垃圾回收(Full GC)。Java8中已经移除了永久代,新加了一个叫做元数据区的native内存区。
- 什么是分布式垃圾回收(DGC)?如何工作?
DGC叫做分布式垃圾回收。RMI使用DGC来做自动垃圾回收。因为RMI包含了跨虚拟机的远程对象的引用,垃圾回收是很困难的,DGC使用引用计数算法来给远程对象提供自动内存管理。
- JVM垃圾处理方法
标记-清除算法(老年代)
该算法分为"标记"和"清除"两个阶段:首先标记处所有需要回收的对象(可达性分析),在标记完成后统一清理掉所有被标记的对象。
该算法的两个问题:
1、效率问题,标记和清除效率不高。
2、空间问题,标记清除后会产生大量不连续的内存碎片,空间碎片太多会导致在运行过程中需要分配较大对象时无法找到足够的连续内存而不得不提前触发另一次垃圾收集。(所以它一般用于垃圾不太多的区域,如老年代)。复制算法(新生代)
该算法的核心是将可用内存按容量划分为大小相等的两块,每次只用其中一块,当这一块的内存用完,就将还存活的对象复制到另外一块上面,然后把已使用过的内存空间一次清理掉。
优点:不用考虑碎片问题,方法简单高效。
缺点:内存浪费严重。
现代商用VM的新生代均采用复制算法,但由于新生代的98%的对象都是生存周期极短的,因此并不需要完全按照1:1的比例划分新生代空间,而是将新生代划分为一块较大的Eden区和两块较小的Survivor区(HotSpot默认Eden和Survivor的大小比例为8:1),每次只用Eden和其中一块Survivor。
当发生Minor GC时,将Eden和Survivor中还存活着的对象一次性拷贝到另外一块Survivor上,最后清理掉Eden和刚才使用过的Survivor的空间,当Survivor空间不够用不足以保存尚存活的对象时,需要依赖老年代进行空间分配担保机制,这部分内存直接进入老年代。
复制算法的空间分配担保:
在执行Minor GC前,VM会首先检查老年代是否有足够的空间存放新生代尚存活对象,由于新生代使用复制收集算法,为了提升内存利用率,只用了一个Survivor作为轮换备份,因此当出现大量对象在MInor GC后仍然存活的情况时,就需要老年代进行分配担保,让Survivor无法容纳的对象直接进入老年代。但前提是老年代有足够空间。
存活对象的大小在实际完成GC前是无法知道的,因此Minor GC前,VM会首先检查老年代连续空间是否大于新生代对象总大小或历次晋升的平均大小,如果条件成立,则进行MInor GC,否则进行Full GC(让老年代腾出更多空间)。
然而取历次晋升的对象平均大小也有风险,如果某次Minor GC存活后的对象突增,远远高于平均值的话,依然可能导致担保失败,此时就只好在失败后重新发起一次Full GC。
标记-整理算法(老年代)
标记清除算法会产生内存碎片问题,而复制算法需要有额外的内存担保空间。标记整理算法的标记过程与标记清除算法相同,但后续步骤不再对可回收对象直接清理,而是让所有存活的对象都向一端移动,然后清理掉端边界以外的内存。
- 简述分代垃圾回收的过程?
分代回收器有两个分区:老生代和新生代,新生代默认的空间占比总空间的1/3,老生代2/3。
新生代使用复制算法,有3个分区;Eden、To Survivor、From Survivor,它们的默认占比是8:1:1,执行流程:
当新生代的Eden区分配满的时候,就会触发新生代的GC:
1)在Eden区执行了第一次GC后,存活的对象会被移到其中From Survivor
2)Eden区再次GC,这时采用复制算法,将Eden和From Survivor一起清理。存活的对象被复制到To Survivor区。之后清空From Survivor区。
- MinorGC,MajorGC,FullGC都发生在什么时候?
MinorGC
在年轻代空间不足时发生,MajorGC
是老年代的GC,出现时伴有MinorGC
。
FullGC
三种情况:
1)当老年代无法再分配内存的时候
2)元空间不足的时候
3)显示调用System.gc的时候。另外,像CMS一类的垃圾回收器,在MinorGC出现promotion failure的时候也会发生FullGC。
- 说出几个垃圾收集器?
Serial收集器
是Hotpost运行在Client模式下的默认新生代收集器,它在收集时会暂停所有进程,用一个线程去完成GC。特点是简单高效,适合JVM管理内存不大的情况。Parnew收集器
是Serial
的多线程版本,配合多核心cpu效果最好。Cms收集器
是一款真正意义上的并发收集器。==优点:==并发收集,低停顿。==缺点:==当cpu数<=4时,过多占用cpu资源;无法清除浮动垃圾。G1
引入分区,弱化分代,合理利用垃圾收集各个周期的资源,解决了CMS的很多缺陷。
- 可达性算法中,哪些对象可作为GC Roots对象?
- 虚拟机栈中引用的对象
- 方法区静态成员引用的对象
- 方法区常量引用的对象
- 本地方法栈JNI引用的对象
8、多线程
- 进程和线程的关系
1)一个线程只能属于一个进程而一个进程可以有多个线程,至少一个。
2)资源分配给进程,同一进程的所有线程共享该进程的所有资源。
3)处理机分给线程。
4)线程在执行过程中,需要协作同步。不同进程的线程间要利用消息通信的办法实现同步。线程是指进程内的一个执行单元,也是进程内的可调度实体。Java中的线程有四种状态分别是:运行、就绪、挂起、结束。
- 如何停止一个正在运行的线程
1、使用退出标志使线程正常退出,也就是
run
方法完成后。
2、使用stop
方法强行终止,但是不推荐,因为stop
和suspend
及resume
都是过时的方法。
3、使用interrupt
方法中断线程。
- 为什么不推荐stop()和suspend()方法?
stop()
方法不安全,它会解除由线程获取的所有锁定,如果对象处于一种不连贯状态,那么其他线程在那种状态下检查它们很难检查出真正问题所在。
suspend()
方法容易发生死锁。目标线程虽然会停下来但仍然持有在这之前获得的锁定。其他线程都不能访问锁定的资源,除非被挂起的线程恢复运行。对任何线程来说,如果它们想恢复线程目标,同时又试图使用任何一个锁定资源就会死锁。所以不应该使用,而应在自己的Thread类中置入一个标志,指出线程应该活动还是挂起。若标志指出线程应该挂起,便用wait()
命其进入等待状态。若标志指出线程应当恢复,则用一个notify()
重新启动线程。
- 多线程创建方式?
继承
Thread
类
实现Runnable
接口
实现Callable
接口通过FutureTask
包装器来创建Thread线程
使用ExecutorService、Callable、Future
实现有返回结果的多线程
- 实现Runnable接口和Callable接口的区别?
Runnable
接口中的run()
方法的返回值是void
,它做的事情只是纯粹地执行方法体中的代码。
Callable
接口中的call()
方法是有返回值的,是一个泛型,和Future、FutureTask
配合可以用来获取异步执行的结果。
- sleep()和wait()有什么区别?
sleep()
属于Thread
类,而wait()
属于Object
类。
sleep()
方法线程不会释放对象锁。
wait()
线程会放弃对象锁,进入等待此对象的等待锁定池,只有针对此对象调用notify()
方法后本线程才进入对象锁定池准备,获取对象锁进入运行状态。
- Thread类中的start()和run()方法有什么区别?
start()
方法被用来启动新创建的线程,而且内部调用了run()
方法,这和直接调用run()
方法的效果不一样。当调用run()
方法的时候,只会在原来的线程中调用,没有新的线程启动,start()
方法才会启动新线程。
- 线程同步的方式?
1、使用
synchronized
修饰的同步方法。阻塞式的同步。会自动释放锁。
2、同步代码块。
Lock锁。能完成synchronized
所实现的所有功能。还有着更精确的线程语义和更好的性能。锁需要程序员在finally语句中手动释放。
- 对于synchronized关键字的了解?如何使用它?
sychronized
关键字解决的是多个线程之间访问资源的同步性,可以保证被它修饰的方法或者代码块在任意时刻只能有一个线程执行。
三种使用方式:
修饰代码块:
指定加锁对象,对给定对象加锁,进入同步代码库前要获得给定对象的锁。
修饰静态方法:
也就是给当前类加锁,会作用于类的所有对象实例,因为静态成员不属于任何一个实例对象。所以如果一个线程A调用一个实例对象的非静态synchronized
方法,而线程B需要调用这个实例对象所属类的静态synchronized
方法是允许的,而访问非静态synchronized
方法占用的锁是当前实例对象锁。
修饰实例方法:
作用于当前对象实例加锁,进入同步代码前要获得当前对象实例的锁。
- 对线程池的理解?
创建线程要花费的资源和时间,如果任务来了才创建线程那么响应时间会变长,而且一个进程能创建的线程有限。为了避免这个问题,在程序启动的时候就创建若干线程来响应处理,它们被称为线程池,里面的线程叫工作线程。
使用线程池的好处
降低资源消耗。
通过重复利用已创建的线程降低线程创建和销毁造成的消耗。
提高响应速度。
当任务到达时,任务可以不需要等线程创建就能立即执行。
提高线程的可管理性。
线程是稀缺资源,如果无限制的创建不仅会消耗系统资源,还会降低系统稳定性,使用线程池可以进行统一分配,调优和监控。
- 常用的线程池有哪些?
newFixedThreadPool
创建一个指定工作线程数量的线程池。每当提交一个任务就会创建一个工作线程,如果工作线程数量达到线程池初始的最大值,则将提交的任务存入到池队列中。
newCachedThreadPool
创建一个可缓存的线程池。工作线程的创建数量几乎没有限制,如果长时间没有往线程池中提交任务,则该工作线程自动终止。之后你又提交新的任务,则线程池重新创建一个工作线程。
newSingleThreadExecutor
创建一个单线程化的Executor,即只创建唯一的工作线程来执行任务,如果这个线程异常结束,会有另一个取代它,保证顺序执行。
newScheduleThreadPool
创建一个定长的线程池,而且支持定时的以及周期性的任务执行。
- 如何创建线程池?
方式一:通过构造方法实现(ThreadPoolExecutor)
方式二:通过Executor框架的工具类Executors来实现FixedThreadPool、SingleThreadExecutor、CacheThreadPool
三种线程池的创建。
- 提交任务时,线程池队列已满,会发生什么?
如果使用的是无界队列
LinkedBlockingQueue
,可以继续添加任务到阻塞队列中等待执行,因为无界队列可近似认为是一个无穷大的队列。
如果使用的是有界队列比如ArrayBlockingQueue
,任务首先会被添加到ArrayBlockingQueue
中,如果满了会根据maximumPoolSize
的值增加线程数量,如果还不行就使用拒绝策略RejectedExecutionHandler
处理。
- 什么是线程安全?如何确保线程安全?Vector是一个线程安全类吗?
如果代码在多线程下执行和在单线程下执行永远都能获得一样的结果,那么就是线程安全的。
对非安全的代码进行加锁控制
使用线程安全的类
多线程并发情况下,线程共享的变量改为方法级的局部变量
虽然源代码注释说是线程安全的,很多方法加上了synchronized
关键字,但是对于复合操作而言,只是同步方法并没有解决线程安全问题。要真正安全要以vector
对象为锁。
- 什么是乐观锁和悲观锁?
乐观锁:
对于并发间操作产生的线程安全问题持乐观状态,认为竞争不总是会发生,因此它不需要持有锁,将比较-替换这两个动作作为一个原子操作尝试去修改内存中的变量,如果失败则表示发生冲突,那么就应该有相应的重试逻辑。
悲观锁:
对于并发间操作产生的线程安全问题持悲观状态,认为竞争总是会发生,因此每次对某资源进行操作时,都会持有一个独占的锁。
10、设计模式
Java中有23种设计模式,其中常用的有:
工厂模式:``BeanFactor
就是简单工厂模式的体现,用来创建对象的实例
单例模式:
Bean默认为单例模式
代理模式:
Spring的AOP
功能用到了JDK
的动态代理和CGLIB
字节码生成技术
模板方法模式:
用来解决代码重复问题,例如RestTemplate、JpaTemplate
观察者模式:
定义对象键一种一对多的依赖关系,当一个对象状态发送改变时,所有依赖与它的对象都会得到通知并自动更新,如Spring中listencer的实现–ApplicationListener