Java基础面试-后台面试(自用笔记,更新中)

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. 有了解多线程吗?说下什么是死锁?代码中是如何解决死锁问题的?

     多线程就是在用一个进程下做多个任务,比如在杀毒软件上边清理垃圾边查找病毒。

     死锁是指多个进程抢夺资源造成的僵持状态。产生死锁的必要条件:互斥条件、请求和保持、不剥夺条件、循环等待条件。

扫描二维码关注公众号,回复: 11608547 查看本文章

     代码中解决死锁问题:(1)定义锁的顺序,并且整个应用中都按照这个顺序来获取锁。就不会出现抱死的情况了(2)

5.  Java中的锁机制介绍一下,重点说synchronizedvolatile

     锁为了保证数据的一致性,主要是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)

    悲观锁:总是假设最坏的情况,每次去拿数据的时候都认为别人会修改,所以每次在拿数据的时候都会上锁,这样别人想拿这个数据就会阻塞直到它拿到锁(synchronizedReentrantLock

8. IO多路复用

9. JavaArrayListLinkedList

    ArrayList:(1)线程不安全的,因为ArrayList添加值一共有两个步骤,首先给object[size]赋值,接下来size++。(2)ArrayList继承AbstractList抽象父类,实现了List接口(3)arrayList缺省数组长度为10,以后的每次扩容都是扩大到原始容量的1.5倍。

    LinkedList:基于双向链表实现,线程不安全

10.Hashmap的实现方式,为什么Hashmap不是线程安全的,说一下hashmap扩容时的头插法和尾插法的区别,为什么头插***导致循环链表?hashmapput方法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. concurrentHashMapHashMaphashtable的区别

      (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?

  

selectpollepoll讲一下

epoll的了解,一直问到具体是如何更改描述符状态的

中间件底层原理知道吗?(MQ

猜你喜欢

转载自blog.csdn.net/zhangzulin1234/article/details/108305309
今日推荐