1. 多态的实现
接口多态性,继承多态性,通过抽象类实现的多态性。
2. Java类的分类,普通类,接口,抽象类的区别
https://www.cnblogs.com/leeego-123/p/11378108.html
抽象类(abstract)解决的是是不是的问题,比如猫和狗都属于动物,就必须继承动物里的方法,且抽象类不能被实例化。
接口(interface)是有没有的问题,比如你有没有吃饭这个动作,有的话就实现(implement)这个接口。
3. Java创建线程的方式。线程的状态
(1)继承Thread类实现多线程 (2)覆写Runnable()接口实现多线程,而后同样覆写run().推荐此方式
(3)覆写Callable接口实现多线程(JDK1.5) (4)通过线程池启动多线程
4. 有了解多线程吗?说下什么是死锁?代码中是如何解决死锁问题的?
多线程就是在用一个进程下做多个任务,比如在杀毒软件上边清理垃圾边查找病毒。
死锁是指多个进程抢夺资源造成的僵持状态。产生死锁的必要条件:互斥条件、请求和保持、不剥夺条件、循环等待条件。
代码中解决死锁问题:(1)定义锁的顺序,并且整个应用中都按照这个顺序来获取锁。就不会出现抱死的情况了(2)
5. Java中的锁机制介绍一下,重点说synchronized、volatile
锁为了保证数据的一致性,主要是Lock和syschronized
对于synchronized,在java代码层面,通过添加关键字synchronized的方式来实现,在字节码层面是通过monitorenter -exit 来实现的,在执行过程中锁会自动升级,刚开始是属于无锁态的,当请求一个资源的时候会添加一个偏向锁(就相当于贴个名字,markword里面贴一个指向当前线程的指针),当出现竞争,即另一个线程也请求这个资源的时候,撤销偏向锁,每个线程在自己的线程栈生成自己的一个对象LockRecord,通过自旋(CAS)的方式来决定哪一个线程获取到锁,进而升级成轻量级锁(自旋锁),当有更多的线程来竞争,则除了拥有锁的线程外,其他线程需要等待时间较长,且不断尝试获得锁,耗费cpu资源,于是锁升级,升级为重量级锁,此时没有获得锁的其他线程在一个队列中(等待队列需要等待操作系统的调度,队列中不消耗cpu资源),轮到谁取谁出来,这样减少了资源的消耗。‘
对于volatile,(volatile并不一定是由缓存一致性协议实现的)volatile轻量级的同步机制,能保证可见性且禁止指令重排序,但不能保证原子性。这些性质怎么实现的呢?:(1)线程间可见性:由于缓存一致性协议,如果一个cpu修改了数据,写回主存储器的过程中,会把其他cpu读到的该数据作废,所以其他cpu再使用该变量的时候需要重新读,(2)禁止指令重排序:内存屏障,
6. 自旋锁的实现方式
自旋锁即通过CAS实现(JUC都是CAS实现的),AtomicInteger对象有一个incrementAndGet()的方法,底层调用了一个CompareAndSwap方法,即CAS,通过不断自旋并读取数据,判断当前读取的数据和之前是否相同,若相同则执行操作,不同则继续自旋,流程图如下:
自旋锁出现的问题:(1)ABA问题解决:加版本号(2)原子性,即自旋锁第二次读取时与第一次数据相同,刚要执行还没执行,其他线程把值改了:最终的实现在汇编上:lock mpxchg ,具体代码如下图,其中lock_if_mp是如果多处理器(多核)则加锁,lock是意思是锁总线,只有我能用,保证原子性。
7. 乐观锁和悲观锁
乐观锁:总是假设最好的情况,每次去拿数据的时候都认为别人不会修改,所以不会上锁,但是在更新的时候会判断一下在此期间别人有没有去更新这个数据,可以使用版本号机制和CAS算法实现。(CAS)
悲观锁:总是假设最坏的情况,每次去拿数据的时候都认为别人会修改,所以每次在拿数据的时候都会上锁,这样别人想拿这个数据就会阻塞直到它拿到锁(synchronized
和ReentrantLock
)
8. IO多路复用
9. Java中ArrayList和LinkedList
ArrayList:(1)线程不安全的,因为ArrayList添加值一共有两个步骤,首先给object[size]赋值,接下来size++。(2)ArrayList继承AbstractList抽象父类,实现了List接口(3)arrayList缺省数组长度为10,以后的每次扩容都是扩大到原始容量的1.5倍。
LinkedList:基于双向链表实现,线程不安全
10.Hashmap的实现方式,为什么Hashmap不是线程安全的,说一下hashmap扩容时的头插法和尾插法的区别,为什么头插***导致循环链表?hashmap的put方法1.8前后区别。
(1)实现方式:Hashmap是一个散列表,kv的,如果没有指定初始容量,那么初始容量就是16。向hashmap中添加元素时,需要先求元素的hashcode,然后根据与table.length-1做与运算得到对应的数组下标,如果当前数组位置没有元素,则添加,否则成链(1.7之前是头插法,1.8后可能是尾插)。负载因子默认为0.75,当这个HashMap储存元素个数达到了总容量的0.75倍时,自动扩容至原来的两倍,当扩容达到最大值时,不在扩容,当数组的某个值的链表长度为8时,自动转化为红黑树。扩容是需要耗费很多资源的,所以尽量在刚开始指定初始容量。
(2)线程不安全原因(待总结):https://blog.csdn.net/mydreamongo/article/details/8960667?depth_1-
(3)扩容时的头插法和尾插法的区别:头插法在多线程的情况下可能导致形成环
(4)hashmap的put方法1.8前后区别:1.7之前是头插法,1.8后是尾插
11. concurrentHashMap、HashMap、hashtable的区别
(1)HashMap:线程不安全,可以存储null键和null值
(2)concurrentHashMap:
- 底层采用分段的数组+链表实现,线程安全
- 通过把整个Map分为N个Segment,可以提供相同的线程安全,但是效率提升N倍,默认提升16倍。(读操作不加锁,由于HashEntry的value变量是 volatile的,也能保证读取到最新的值。)
- Hashtable的synchronized是针对整张Hash表的,即每次锁住整张表让线程独占,ConcurrentHashMap允许多个修改操作并发进行,其关键在于使用了锁分离技术
- 有些方法需要跨段,比如size()和containsValue(),它们可能需要锁定整个表而而不仅仅是某个段,这需要按顺序锁定所有段,操作完毕后,又按顺序释放所有段的锁
- 扩容:段内扩容(段内元素超过该段对应Entry数组长度的75%触发扩容,不会对整个Map进行扩容),插入前检测需不需要扩容,有效避免无效扩容
(3)hashtable:无论key还是value都不能为null,线程安全,实现线程安全的方式是在修改数据时锁住整个HashTable,效率低,
12. currenthashmap怎么扩容的?fwn为什么固定hash -1,这样设计有什么好处
(1)currenthashmap怎么扩容的?见11
(2)fwn为什么固定hash -1?
select、poll、epoll讲一下
对epoll的了解,一直问到具体是如何更改描述符状态的
中间件底层原理知道吗?(MQ)