- 1. java基础面试16问
- 1.1. 1.进程和线程的区别
- 1.2. 2.synchronized原理
- 1.3. 3.锁的优化机制
- 1.4. 4.对象头都包含那些内容
- 1.5. 5.reentrantlock原理,和syn区别
- 1.6. 6.cas原理
- 1.7. 7.cas问题
- 1.8. 8.hashmap原理
- 1.9. 9.多线程怎么用map?有了解concurrenthashmap吗
- 1.10. 10.volatile原理你知道吗
- 1.11. 11. jmm有什么意义
- 1.12. 12.工作内存和主内存是什么
- 1.13. 13.threadlocal是什么
- 1.14. 14.引用,强软弱虚
- 1.15. 15. 线程池的核心
- 1.16. 16. 拒绝策略有哪些
java基础面试16问
1.进程和线程的区别
进程是系统的资源分配和调度的独立单元,可以并发执行,提高利用率和吞吐率。但是创建销毁花销大,所以不能太多。
线程比进程更小,减小开销。操作系统有更好并发。基本不拥有系统资源,除了pc, 寄存器和栈。进程则有栈和堆。
2.synchronized原理
使用后,会在编译之后【会在同步代码块】加monitorenter和monitorexit。
【jdk6之前】,原理是使用了操作系统的互斥锁,和系统的线程一一对应,【线程被阻塞和唤醒都会从用户态变内核态】,所以开销不小。
当遇到Monitorenter时候,【会尝试拿锁】,拿到计数器会加1,排他锁就会【把其他线程放入等待队列】。
当Monitorexit的时候,计数器减1,直至0,【那等待队列里面的线程就会去竞争这个锁】。
【从内存语义来说,加锁就是清空工作内存的值并读主内存的值去工作内存。释放锁就是把工作内存的值写回主内存。】
【从源码来说,syn就是有两个队列waitset和entrylist。
进入同步代码块,线程进入entrylist。
尝试拿锁,当前线程设成它,然后计数器加一。
当wait就释放锁,当前线程设Null,计数器减一,进入到waitset。当Notify或者all就重新进入entrylist竞争。
执行完毕,同样释放锁,当前线程为Null,计数器-1。】
3.锁的优化机制
首先jdk6后,锁开始分等级了,一开始是无锁,然后偏向锁,然后轻量级锁,最后重量级锁。而在这个过程中,有几个优化的机制:自旋锁,自适应锁,偏向锁,轻量级锁,锁消除,锁粗化。
自旋锁就是因为很多时候锁占用时间少,为了减少从用户态到内核的切换,就让线程一直干空活来免除这种切换。
自适应就是自旋的次数自适应了,由前一个线程来决定自旋多少次。
偏向锁就是【在对象头和栈帧记录线程id,如果一样就不用cas了】如果没有其他线程竞争锁,那对象锁头就会一直标记这个线程。
轻量级锁就是每次改对象锁头时候就是用cas的方式去做来获取锁,【拿不到就自旋再试】,就可以减少了一下子变成重量级锁那样要切换到内核态。在用户态就可以判断是否可以拿到锁了。
如果竞争很激烈,就会升级为重量级锁。毕竟很多个线程cas也是很耗资源的,还不如重量级锁那样都阻塞了。
锁消除就是判断代码里是不是不涉及资源竞争,那就不用去加锁解锁了。
锁粗话就是判断代码里是不是很经常加锁解锁在连续的代码里,如果是,就先加锁一并处理完再解锁。
4.对象头都包含那些内容
一般对象分为3部分:对象头,对象数据,对齐填充
而对象头包含至少2部分:
- 一些锁或gc信息:hash值,分代年龄,gc标志,偏向锁id,偏向锁时间戳,轻量级锁指针,重量级锁指针
- 代表的实例的指针,通过这个指针知道这个对象是什么类
- 如果是数组,还有数据长度
5.reentrantlock原理,和syn区别
原理就是依赖aqs。首先有个volatile变量state来记录加锁数量,然后记下目前的线程id,cas操作来加锁,加锁成功其他就进入【阻塞队列自旋】。直至该变量为0,把线程id=null,才【唤醒阻塞队列的线程】。
在jdk7,8。性能区别不大了。
区别:
- lock需要显示加锁,放锁。
- sync是关键字,Lock是api类
- lock可以中断锁,可以公平锁
- lock可以绑定condition来指定通知哪个线程。而syn的notify, notifyall是随机的。
【5.sync是悲观同步阻塞,lock是cas自旋阻塞
6.cas原理
compare and swap。
- 比较旧值和内存值是否一样。
- 一样就,新值放内存值。
- 不一样就,不换。
7.cas问题
- aba问题,就是虽然旧值和内存值一样,但可能中间已经经历过很多次换值了。所以可以用版本号来做比较
- 循环次数多耗资源。这个也是为什么轻量级锁升级为重量级锁,因为太多的循环还不如直接阻塞了。
- 多个值的话,要使用atomicReference或者sync来做
8.hashmap原理
由数据加链表组成。不是线程安全的。jdk7和jdk8区别主要在:
- 以前头插容易死循环,现在尾插
- jdk8链表长度大于8就会变红黑树,减少查询时间
至于操作,三点
put的话,就是先来位操作看hash值,然后该位置没有就放在那,有则【判断Key是否相同,不同】加入到该链表尾部,【同则覆盖】。大于8则变成红黑树。
get的话,就是看hash值然后遍历链表或者红黑树。
扩容,就是重新计算hash值来移到新数组。【超过长度*负载因子 16 * 3/4=12】
9.多线程怎么用map?有了解concurrenthashmap吗
用concurrecthashmap比hashtable或者synchronizedMap性能好。因为后两者都是操作上整体加锁,并发为1。concurrect的话并发率提高到至少默认值的16。
原理在jdk7和8有点不同。7是分段锁,数组是段数组,段里面是hashentry数组,而段是继承retreenlock,然后操作上来只需要对段加锁就好了。【但是value是volatile,所以不用锁。】
8是没了段,是用node数组,【就是并发去到数组元素,比段更大】,然后操作是用cas和sync来操作。没值就cas来初始化。有值的话,就用sync来加值。
10.volatile原理你知道吗
volatile保证了可见性和禁止指令重排。一般来说先从主内存读数据到工作内存,然后工作,最后把工作内存写回主内存。而如果多线程修改同一个值就有问题了。【这是线程a的工作内存读不了线程B的工作内存的值。就会不一致了。所以volatile就是强制每次读主内存的值。】
而内存屏障就是volatile保证可见性能正确执行。就是保证volatile读写和普通读写不会重排序。
11. jmm有什么意义
电脑里面本身cpu和内存速度差别大,所以出现了缓存,而因为多级缓存,所以就产生了缓存一致性协议,不同cpu有不同实现,也产生了可见性问题。【而编译器和cpu地重排序带来原子性和有序性问题】。jmm内存模型就是让java屏蔽了不同cpu之间缓存一致性协议而存在的。保证用户都是在不同硬件一样地去实现高速并发。
原子性:sync,原子类解决
可见性,有序性就是volatile,final,sync.
12.工作内存和主内存是什么
主内存就是物理内存,工作内存就是缓存。可能是寄存器的,可能是三级缓存。
13.threadlocal是什么
就是线程本地变量,每个线程一个。threadlocal里面有个【静态内部类】threadlocalmap,就是entry数组的弱引用。而key指向threadlocal的【弱引用】。所以如果value没有及时remove,可能会内存泄漏。至于弱引用的原因就是希望下一次gc可以回收。为什么是静态内部类?【当外部类需要使用内部类,而内部类无需外部类资源,并且内部类可以单独创建,这时我们会考虑采用静态内部类的设计。类似Builder模式 https://blog.csdn.net/Jacky_chenjp/article/details/74791842】
14.引用,强软弱虚
强就是new,内存溢出不回收。软就是softreference, 如果内存满就会回收。.然后弱引用就是weakREFERENCE,不满也回收。虚引用就是用来管理对外内存。
15. 线程池的核心
五个核心概念:
- core pool size
- max pool size
- blockqueue
- keep alive time
- reject handler
一开始先放core运行,放满了再放block queue,也满了再开max数量的线程来处理。处理完所有之后keep alive time之后回收多余的Max线程,恢复到core线程数。如果max开了也处理不完blockqueue,再有新的就会按reject handler来拒绝。
16. 拒绝策略有哪些
- abort抛出异常
- discardOldest抛弃最旧的
- discard直接扔掉
- 还有一个是谁创建线程谁执行,[callerruns]