1:String 特性 、StringBuffer 和StringBuilder的区别
String str = "abc"; "abc"--> 保存在常量池中
String str = new String(“abc”) ---> 保存在堆栈中
== 比较引用类型的地址是否相同
equals 原本比较引用类型,但是在String类中进行重写,比较内容是否相同。先判断长度,在对每位进行判断
String 是被final 修饰的类,不可变。如果需要频繁修改那就使用StringBuffer 、 StringBuilder 。StringBuffer效率高、线程不安全。StringBuilder线程安全
2:ArrayList 和 LinkedList 原理、区别以及底层数据结构
1.ArrayList是实现了基于动态数组的数据结构,LinkedList基于链表的数据结构。
2.对于随机访问get和set,ArrayList优于LinkedList,因为ArrayList可以随机定位,而LinkedList要移动指针一步一步的移动到节点处。(参考数组与链表来思考)
3.对于新增和删除操作add和remove,LinedList比较占优势,只需要对指针进行修改即可,而ArrayList要移动数据来填补被删除的对象的空间。
实现ArrayList线程安全 可以加 synchronized 关键字修饰,或者Collections.synchronizedList() 方法
List<Map<String,Object>> data=new ArrayList<Map<String,Object>>()
3:HashMap、HashTable、ConcurrentHashMap 原理、源码、数据结构。线程是否安全
HashMap 散列,无序。在数据量小的时候,HashMap是按照链表的模式存储的。当数据量变大之后,为了进行快速的查找,会将这个链表变成红黑树(均衡二叉树),用hash码作为数据的定位来进行保存。
HashTable设计上和HashTable 类似,区别:
ConcurrentHashMap的特点=HashMap的高性能+Hashtable的线程安全性。ConcurrentHashMap可以保证多个线程更新数据的同步,又可以保证很高效的查询速度。对Map循环遍历需要拿到KeySite(map.entrySet()),对Key进行筛选、遍历
4:Lock 和 Synchroinze实现原理与区别,简述乐观锁,悲观锁,分布式锁实现方式
悲观锁 |
每次去拿数据的时候都认为别人会修改,所以每次在拿数据的时候都会上锁,这样别人想拿这个数据就会block直到它拿到锁。 |
|
5:SpringMVC处理流程。
1、DispatcherServlet前端控制器接收发过来的请求,交给HandlerMapping处理器映射器
2、HandlerMapping处理器映射器,根据请求路径找到相应的HandlerAdapter处理器适配器(处理器适配器就是那些拦截器或Controller)
3、HandlerAdapter处理器适配器,处理一些功能请求,返回一个ModelAndView对象(包括模型数据、逻辑视图名)
4、ViewResolver视图解析器,先根据ModelAndView中设置的View解析具体视图
5、然后再将Model模型中的数据渲染到View上
这些过程都是以DispatcherServlet为中轴线进行的。
6:junit用法,before,beforeClass,after, afterClass的执行顺序
@BeforeClass -> @Before -> @Test -> @After -> @AfterClass;
@Before:初始化方法 对于每一个测试方法都要执行一次(注意与BeforeClass区别,后者是对于所有方法执行一次)
@After:释放资源 对于每一个测试方法都要执行一次(注意与AfterClass区别,后者是对于所有方法执行一次)
@Test:测试方法,在这里可以测试期望异常和超时时间
@Test(expected=ArithmeticException.class)检查被测方法是否抛出ArithmeticException异常
@Ignore:忽略的测试方法
@BeforeClass:针对所有测试,只执行一次,且必须为static void
@AfterClass:针对所有测试,只执行一次,且必须为static void
7:分布式锁
基于数据库实现分布式锁;
基于数据库的实现方式的核心思想是:在数据库中创建一个表,表中包含方法名等字段,并在方法名字段上创建唯一索引,想要执行某个方法,就使用这个方法名向表中插入数据,成功插入则获取锁,执行完成后删除对应的行数据释放锁。
基于缓存(Redis等)实现分布式锁;
(1)获取锁的时候,使用setnx加锁,并使用expire命令为锁添加一个超时时间,超过该时间则自动释放锁,锁的value值为一个随机生成的UUID,通过此在释放锁的时候进行判断。
(2)获取锁的时候还设置一个获取的超时时间,若超过这个时间则放弃获取锁。
(3)释放锁的时候,通过UUID判断是不是该锁,若是该锁,则执行delete进行锁释放。
基于Zookeeper实现分布式锁
(1)创建一个目录mylock;
(2)线程A想获取锁就在mylock目录下创建临时顺序节点;
(3)获取mylock目录下所有的子节点,然后获取比自己小的兄弟节点,如果不存在,则说明当前线程顺序号最小,获得锁;
(4)线程B获取所有节点,判断自己不是最小节点,设置监听比自己次小的节点;
(5)线程A处理完,删除自己的节点,线程B监听到变更事件,判断自己是不是最小的节点,如果是则获得锁。
8:用hashmap实现redis有什么问题(死锁,死循环,可用ConcurrentHashmap)
Hashmap 本事属于数组+链表的实现方式,在分布式环境下put 操作可能会出现错误判断。put操作主要判断是否为空,key的hashcode执行一次HashMap自己的哈希函数,得到bucketindex位置,还有对重复key的覆盖操作。
Redis sorted set的内部使用HashMap和跳跃表(SkipList)来保证数据的存储和有序,HashMap里放的是成员到score的映射,而跳跃表里存放的是所有的成员,排序依据是HashMap里存的score,使用跳跃表的结构可以获得比较高的查找效率,并且在实现上比较简单。
9:线程的状态
1. 初始(NEW):新创建了一个线程对象,但还没有调用start()方法。
2. 运行(RUNNABLE):Java线程中将就绪(ready)和运行中(running)两种状态笼统的称为“运行”。
线程对象创建后,其他线程(比如main线程)调用了该对象的start()方法。该状态的线程位于可运行线程池中,等待被线程调度选中,获取CPU的使用权,此时处于就绪状态(ready)。就绪状态的线程在获得CPU时间片后变为运行中状态(running)。
3.阻塞(BLOCKED):表示线程阻塞于锁。
4.等待(WAITING):进入该状态的线程需要等待其他线程做出一些特定动作(通知或中断)。
5.超时等待(TIMED_WAITING):该状态不同于WAITING,它可以在指定的时间后自行返回。
6. 终止(TERMINATED):表示该线程已经执行完毕。
10:线程阻塞的方式
线程阻塞原因有多种:等待I/O操作结束,等待获得一个锁,等待从Thread.sleep方法中醒来,或是等待另一个线程的计算结果。当线程阻塞时,它通常被挂起,并处于某种阻塞状态(BLOCKED, WAITING或TIMED_WATING)。阻塞操作与执行时间很长的普通操作的差别在于,被阻塞的线程必须等待某个不受它控制的事件发生后才能继续执行,例如等待I/O操作完成,等待某个锁变成可用,或者等待外部计算的结束。当某个外部事件发生时,线程被置回RUNNABLE状态,并可以再次被调度执行。
11:sleep和wait的区别
对于sleep()方法,我们首先要知道该方法是属于Thread类中的。而wait()方法,则是属于Object类中的。
sleep()方法导致了程序暂停执行指定的时间,让出cpu该其他线程,但是他的监控状态依然保持者,当指定的时间到了又会自动恢复运行状态。
在调用sleep()方法的过程中,线程不会释放对象锁。
而当调用wait()方法的时候,线程会放弃对象锁,进入等待此对象的等待锁定池,只有针对此对象调用notify()方法后本线程才进入对象锁定池准备
12:hashmap的底层实现
hashMap 底层实现为 数组+链表实现
13:java内存模型,垃圾回收机制,不可达算法
Java内存模型结构分为
- 线程共享区:堆、方法区
- 线程私有区:虚拟机栈、本地方法栈、程序计数器
堆:用于存放对象实例和数组,由于堆是用来存放对象实例,因此堆也是垃圾收集器管理的主要区域,故也称为 GC堆。由于现在的垃圾收集器基本都采用分代收集算法,所以堆的内部结构只包含新生代和老年代。
方法区:用于存储已被虚拟机加载的类信息、常量、静态变量、即时编译器编译后的代码等数据。
- 方法区通常和永久区(Perm)关联在一起,但永久代与方法区不是一个概念,只是有的虚拟机用永久代来实现方法区,这样就可以用永久代GC来管理方法区,省去专门内存管理的工作
- 根据Java虚拟机规范的规定,当方法区无法满足内存分配的需求时,将抛出 OutOfMemoryError 异常
虚拟机栈
- 每个方法在执行的时候都会创建一个栈帧,用于存储局部变量表、操作数栈、动态链接、方法出口等信息
- 每个方法从调用直至完成的过程,对应一个栈帧在虚拟机栈中入栈到出栈的过程
- 局部变量表主要存放一些基本类型的变量和对象句柄,它们可以是方法参数,也可以是方法的局部变量
程序计数器
为什么需要程序计数器?
在多线程情况下,当线程数超过CPU数量或CPU内核数量时,线程之间就要根据时间片轮询抢夺CPU时间资源。也就是说,在任何一个确定的时刻,一个处理器都只会执行一条线程中的指令。因此,为了线程切换后能够恢复到正确的执行位置,每条线程都需要一个独立的程序计数器去记录其正在执行的字节码指令地址
程序计数器是线程私有的一块较小的内存空间,可以看做是当前线程所执行的字节码的行号指示器
- 如果线程正在执行的是一个 Java 方法,计数器记录的是正在执行的字节码指令的地址
- 如果正在执行的是 Native 方法,则计数器的值为空
- 程序计数器是唯一一个没有规定任何 OutOfMemoryError 的区域
---------------------------------------------注:有些内容网上CP,侵权删