Java八股文

267 阅读5分钟
  1. 数组和链表比较,时间复杂度,线程安全性 ArrayList和LinkedList都不是线程安全的

  2. 单向链表和双向链表的区别以及使用场景,跳表的复杂度,使用场景 双向链表比单向链表多个指向前驱节点的指针。单向链表可以用作队列,双向链表可以作双端队列和栈。 跳表的查找和插入时间复杂度都是O(logn)。 跳表是在单链表的基础上添加了多层指针作为索引,由下到上逐渐稀疏。 查找时,从上到下,需要遍历的次数变少了,空间换时间。 redis的zset在skiplist编码下用到了跳表。

  3. 二叉树、AVL树、B树、B+树、红黑树的区别以及常用API复杂度以及使用场景 二叉树是一棵空树或者每个节点最多只能有两个子树的有序树,左边的称为左子树,右边的称为右子树。

二叉搜索树是一棵具有以下性值的二叉树,若它的左子树不空,则左子树上所有节点的值均小于它的根节点的值,若它的右子树不空,则右子树上所有节点的值均大于它根节点的值;它的左右子树也分别为二叉搜索树。

AVL树即平衡二叉搜索树,首先它是一棵二叉搜索树,其次它每个节点的左右子树高度差不大于1。

B树又叫平衡多路查找树, (1)所有节点按关键字递增排列; (2)非叶子节点的子节点树大于1小于M,M表示M路B树; (3)非叶节点的关键字数量小于等于M,大于等于M/2-1; (4)所有叶子节点均在同一层。

B+树与B树有以下不同:(1)B+树非叶节点不保存记录,只存键值;(2)B+树叶子节点保存了父节点所有关键字及数据,因此必须遍历到叶子节点才能取到数据;(3)B+树叶节点按照关键字从小到大排序,并通过双向指针连接。 所以B+树相比于B树的优点是:层级少,查询稳定,范围查找方便

红黑树5大性质: (1)节点是红或者黑的 (2)根节点是黑的 (3)叶节点是黑的 (4)红节点的两个子节点都是黑的 (5)从任一节点到其每个叶节点上的所有路径黑节点个数相同

  1. List线程安全性,如何升级线程安全性API,举例对应的数据结构,CopyOnWrite原理 List升级线程安全API的方法: (1)使用Collections.synchronizedList(); (2)使用CopyOnWriteArrayList copyonwrite:在写数据的时候拷贝一份副本写,写完之后将引用指向新副本。适用于读多写少的场景。

  2. CAS原理,JVM锁,synchronized原理 CAS意思是比较并交换,是一种乐观锁。CAS首先读取了变量的值,修改的时候先比较变量现在的值是否等于旧值,相等时通过原子方式更新变量值。 CAS底层借助CPU指令实现无锁自增。

锁升级过程: 当不存在锁竞争时,一个线程想要获取锁,会进入偏向锁状态,偏向锁是通过在对象头中的markword中存放偏向线程的id实现的; 当出现锁争用时,会进入到轻量级锁状态,轻量级锁是通过自旋锁也就是循环CAS实现的,轻量级锁的好处是可以避免线程状态切换带来的性能开销,但是有忙等问题,所以适用于并发量小且同步代码块执行时间短的情况;(轻量级锁的markword存放指向线程栈帧中锁记录的指针) 当自旋次数超过一定阈值,轻量级锁会膨胀成重量级锁,征用重量级锁的线程如果拿不到锁会立即进入锁阻塞状态而避免忙等带来的性能开销,适用于并发量大或同步块执行时间长的情况。

synchronized原理: JVM是通过进入、退出对象监视器Monitor来实现对方法、同步块的同步的,而对象监视器本质依赖于底层操作系统的互斥锁Mutex实现。 具体是在编译后在同步方法调用前加入一个monitor.enter指令,在退出方法和异常处插入monitor.exit指令。 没有获取到锁的方法将会阻塞到方法入口处。

volatile原理: 当对volatile变量执行写操作后,JMM会把工作内存中的最新变量值强制更新到主内存; 对volatile变量的写操作会导致其它线程中的缓存无效。 volatile是通过编译器在生成字节码时,在指令序列中添加内存屏障来禁止指令重排序的。

ThreadLocal原理: ThreadLocal变量是线程局部变量,同一个ThreadLocal所包含的对象在不同Thread中有不同的副本。 每个Thread中都有一个ThreadLocalMap来存放线程本地变量,每当使用ThreadLocal get/set实际上是在这个map上get/set,key指向当前ThreadLocal对象,value是Object。

key是指向ThreadLocal对象的弱引用。如果是强引用,只有当线程执行结束才会回收ThreadLocal对象。如果是弱引用,当ThreadLocal的引用是局部变量,执行完就可以在下次gc时回收掉ThreadLocal对象。

在使用完ThreadLocal后,执行remove操作,避免出现内存泄漏。

  1. LRU LRU即最近最少使用,是一种缓存淘汰算法。当需要淘汰一个缓存时,取未使用时间最长的予以淘汰。 LFU即最近最不常使用,淘汰使用频率最低的。

LRU借助map和deque双向链表实现。代码:

class LRUCache {

    private Map<Integer, Integer> map;
    private Deque<Integer> keyDeque;
    private int capacity;

    public LRUCache(int capacity) {
        map = new HashMap<>();
        keyDeque = new LinkedList<>();
        this.capacity = capacity;
    }
    
    public int get(int key) {
        Integer i = map.get(key);
        if (i == null) {
            return -1;
        }
        keyBeFirstIfNot(key);
        return i.intValue();
    }
    
    public void put(int key, int value) {
        if (map.get(key) == null) {
            if (map.size() >= capacity) {
                Integer i = keyDeque.pollLast();
                map.remove(i);
            }
            keyDeque.offerFirst(key);
        } else {
            keyBeFirstIfNot(key);
        }
        map.put(key, value);
    }

    private void keyBeFirstIfNot(int key) {
        if (keyDeque.peekFirst() != key) {
            keyDeque.remove(key);
            keyDeque.offerFirst(key);
        }
    }
}
  1. 单例懒汉式-double check
public class Singleton {

    private static volatile Singleton instance;

    private Singleton() {
    }
    
    public Singleton getInstance() {
        if (instance == null) {
            synchronized (Singleton.class) {
                if (instance == null) {
                    instance = new Singleton();
                }
            }
        }
        return instance;
    }
}

注意: (1)私有化构造函数 (2)私有静态实例变量 (3)双检锁

  1. AQS AQS可以实现独占锁和共享锁。