面试重点。

基础篇

一、面向对象三大特性

封装、继承、多态

二、集合
  1. ArrayList和LinkedList区别
    • ArrayList基于动态数组实现。支持随机访问,但插入删除的代价很高,需要移动大量元素
    • LinkedList基于双向链表实现。不支持随机访问,但插入删除方便,只需要改变指针
  2. HashMap实现原理
    • HashMap的内部存储结构其实是数组+链表+树。当实例化一个HashMap时,会初始化默认长度(initialCapacity)和加载因子(loadFactor),系统会创建一个长度为默认长度(initialCapacity)的Node数组,这个长度在哈希表中被称为容量(Capacity),在这个数组中可以存放元素的位置称之为“桶”(bucket),每个桶(bucket)都有自己的索引,系统可以根据索引快速的查找桶(bucket)中的元素。
    • 每个桶(bucket)中存储一个元素,即一个Node对象。每一个Node对象可以带一个引用变量next,用于指向下一个元素。因此在一个桶(bucket)中,就有可能生成一个Node链,也可能是一个个TreeNode对象,每个TreeNode对象可以有两个叶子节点left和right。因此在一个桶(bucket)中就有可能生成一个TreeNode树。新添加的元素作为链表的last或树的叶子节点。
    • HashMap扩容 :当HashMap中的元素个数超过数组大小(initialCapacity)加载因子(loadFactor)时,就会进行数组扩容,加载因子(loadFactor)的默认值是0.75。默认情况下,数组大小(initialCapacity)为16,那么当HashMap中元素个数超过160.75=12(此值为代码中的threshold值,即临界值)时,就把数组的大小扩展为2*16=32,即扩大一倍,然后重新计算每个元素在数组中的位置,这是一个非常消耗性能的操作,所以如果我们已经能预知HashMap中元素的个数,那么预设元素的个数能够有效的提高其性能。
    • HashMap树形化 :当HashMap中其中一个链的对象个数如果达到了8个,此时如果capacity没有达到64,那么HashMap会先扩容解决。若达到64,那么这个链会转化为树,节点类型由Node变为TreeNode。如果对象被删除后,下次resize方法时判断树的节点个数低于六个,则会把树转化为链表。
    • JDK1.8相较于之前的变化
      • HashMap map = new HashMap();//默认情况下,先不创建长度为16的数组
      • 当首次调用map.put()时,再创建长度为16的数组
      • 数组为Node类型,在jdk7中称为Entry类型
      • 形成链表结构时,新添加的key-value对在链表的尾部(七上八下)
      • 当数组指定索引位置的链表长度>8时,且map中的数组的长度> 64时,此索引位置上的所有key-value对使用红黑树进行存储。
    • 负载因子值的大小,对HashMap有什么影响?
      • 负载因子的大小决定了HashMap的数据密度。
      • 负载因子越大密度越大,发生碰撞的几率越高,数组中的链表越容易长,成查询或插入时的比较次数增多,性能会下降。
      • 负载因子越小,就越容易触发扩容,数据密度也越小,意味着发生碰撞的几率越小,数组中的链表也就越短,查询和插入时比较的次数也越小,性能会更高。但是会浪费一定的内容空间。而且经常扩容也会影响性能,建议初始化预设大一点的空间。
      • 按照其他语言的参考及研究经验,会考虑将负载因子设置为0.7~0.75,此时平均检索长度接近于常数。
    • 谈谈你对HashMap中put/get方法的认识?如果了解再谈谈HashMap的扩容机制?默认大小是多少?什么是负载因子(或填充比)?什么是吞吐临界值(或阈值、threshold)?
      • put():
        1. 当put(k,v)时,创建一个长度为16的Node数组
        2. 首先按照k所在类的hashcode()计算其哈希值,用哈希值计算出k位于哪个桶
          • 如果该桶为空,则直接插入
          • 如果该桶不为空
            • 如果该桶使用的是红黑树,则调用红黑树的方法插入
            • 用链式方法插入。如果链的长度达到临界值,则把链表转化为红黑树
          • 如果桶中存在重复的k,则为该k替换新值
        3. 如果size大于阈值,则进行扩容
      • get():
        1. 通过哈希值找到该k映射的桶
        2. 桶上的k就是要查找的k,直接命中
        3. 桶上的 key 不是要查找的 key,则查看后续节点:
          • 如果后续节点是树节点,通过调用树的方法查找该 key
          • 如果后续节点是链式节点,则通过循环遍历链查找该 key
      • 扩容机制 :默认容量为16,负载因子为0.75,如果使用容量超过16×0.75=12(阈值)时,进行扩容,扩容至一倍(2×16=32),并重新计算每个元素的位置
  3. 创建HashSet会创建一个初始值为16,负载因子为0.75的HashMap,key为add()的值,value为Object类型常量
  4. HashMap和HashTable区别
    • HashTable中的方法是synchronized的
    • HashMap可以插入键为null,HashTable不可以
    • HashMap不能保证对着时间的推移Map中的元素次序是不变的
    • HashTable直接使用对象的hashCode,HashMap要重新计算

并发篇

一、java如何开启线程?如何保证线程安全?
  1. 线程和进程的区别 :进程是操作系统进行资源分配的最小单元,线程是操作系统进行任务分配的最小单元,线程属于进程
  2. 进程间通信方式:
    • 管道通信: 一种半双工的通信方式,数据只能单向流动,且只能在具有亲缘关系的进程间使用。进程的亲缘关系通常指父子进程关系
    • 命名管道通信:与管道通信相似,但是它允许无亲缘关系进程间的通信
    • 信号量: 是一个计数器,可以用来控制多个进程对共享资源的访问。它常作为一种锁机制,防止多个进程访问同一资源。主要作为进程间以及不同线程之间的同步手段
    • 消息队列:由消息产生的链表,存放在内核中并由消息队列标识符标识。其克服了信号量传递少、管道只能承载无格式字节流以及缓冲区受限等缺点
    • 共享内存:就是映射一段能被其他进程所访问的内存,这段共享内存由一个进程创建,但多个进程可以访问。共享内存是最快的IPC方式,它使很对其他进程间通信方式运行效率低而专门设计的。它往往与其他通信机制来配合使用来实现进程间的同步和通信
    • 套接字:可以用于本地进程间通信和不同主机进程间通信
  3. 线程通信方式:
    • 共享内存:线程之间通过读写内存中的公共状态来隐式通信
    • 消息传递:线程之间没有公共状态,通过明确的发送信息来显示的进行通信
    • 管道流
  4. 开启线程4种方式:
    • 继承Thread类,重写run方法
    • 实现Runnable接口,实现run方法
    • 实现Callable接口,实现call方法
    • 创建线程池
  5. 保证线程安全:加锁
    • JVM提供的锁:Synchronized关键字
    • JDK提供的锁:Lock
  6. 线程的状态
    • 新建:创建一个线程但还没有调用start方法
    • 就绪:调用start方法,进入队列等待cpu时间片,具备运行条件,没有被分配到cpu资源
    • 运行:就绪的线程获取到cpu资源
    • 阻塞:遇到锁或人为挂起时,让cpu临时中止自己的执行
    • 死亡:线程完成了它的任务或被提前强制性中止或出现异常导致结束
二、Volatile和Synchronized有什么区别? Volatile能不能保证线程安全?DCL(Double Check Lock)单例为什么要加Volatile?
  • Synchronized用来加锁,Volatile保证变量的内存可见性
  • 不能。Volatile不能保证原子性
  • Volatile可以防止指令重排
三、JAVA线程锁机制是怎样的?偏向锁、轻量级锁、重量级锁有什么区别?锁机制是如何升级的?
  • JAVA的锁就是在对象头中记录一个锁状态
  • JAVA的锁机制就是根据资源竞争的激烈程度不断进行锁升级的过程
四、谈谈你对AQS的理解。AQS如何实现可重入锁?
  • AQS(抽象队列同步器)是一个JAVA线程同步框架,是JDK中很多锁工具的核心实现框架。在AQS中,维护了一个信号量state和一个线程组成的双向链表。其线程队列就是用来给线程排队的,而state就像是一个红绿灯来控制线程等待或放行。在不同的场景下有不同的意义。
  • 在可重入锁下,state用来标识加锁的次数。0表示无锁,每加一次锁state加1,每释放一次state减1。
五、CAS是什么?CAS底层原理?为什么不用Synchronized而用CAS?CAS缺点?ABA问题?
  • CAS(CompareAndSwap,比较并交换) :是一条CPU并发原语,功能是判断内存某个位置的值是否为预期值,如果是则更改为新的值,这个过程是原子的
  • 底层原理: JAVA的CAS操作都依赖UnSafe类中的方法,UnSafe时CAS的核心类,其中含有大量的native方法,可以直接调用操作系统底层资源操作特定内存中的数据
  • 比较: Synchronized只允许有一个线程访问,保证一致性但没有提高并发性,而CAS既保证了一致性,又提高了并发性
  • CAS缺点:
    1. 循环时间长,CPU开销大
    2. 只能保证一个共享变量的原子操作
    3. ABA问题: 因为 CAS 算法是在某一时刻取出内存值然后在当前的时刻进行比较,中间存在一个时间差,在这个时间差里就可能会产生 ABA 问题。
      • 当有两个线程 T1 和 T2 从内存中获取到值A,线程 T2 通过某些操作把内存 值修改为B,然后又经过某些操作将值修改为A,T2退出。线程 T1 进行操作的时候 ,使用预期值同内存中的值比较,此时均为A,修改成功退出。但是此时的A以及不是原先的A了
      • 解决ABA问题:
        • AtomicReference类,原子引用
        • AtomicStampedReference,时间戳原子引用
六、集合类不安全,ConcurrentHashMap原理?
  • 故障现象: java.util.ConcurrentModificationException
  • 解决方案: 以ArrayList为例
  • new Vector
  • Collections.synchronizedList()
  • CopyOnWriteArrayList(),写时拷贝,(map是ConcurrentHashMap(其原理是使用了分段锁))
七、各种锁
  • 公平锁与非公平锁: ReentrantLock(默认非公平锁)
    • 公平锁: 按申请锁的顺序来获取锁。排队,先来后到
    • 非公平锁: 后申请锁的线程有可能比先申请的线程优先获取锁。高并发下可能出现优先级反转或饥饿现象
  • 可重入锁(递归锁) :A同步方法中调用同步方法B,B也可以获得A的锁,A和B拥有同一把锁
    • Synchronized和ReentrantLock都是可重入锁
  • 自旋锁(SpinLock): 尝试获取锁的线程不会立即阻塞,而是采用循环的方式去尝试获取锁
  • 独占(写)锁: 该锁只能被一个线程所持有
  • 共享(读)锁: 该锁可被多个线程所持有
八、CountDownLatch、CyclicBarrier、Semaphore、LockSupport
  • CountDownLatch: 一个同步工具类,用来协调多个线程之间的同步,或者说起到线程之间的通信。它能够使一个线程在等待另外一些线程完成各自工作之后,再继续执行。使用一个计数器进行实现。计数器初始值为线程的数量。当每一个线程完成自己任务后,计数器的值就会减一。当计数器的值为0时,表示所有的线程都已经完成一些任务,然后在CountDownLatch上等待的线程就可以恢复执行接下来的任务
  • CyclicBarrier: 与CountDownLatch相反。初始值为0,达到要求后才可以恢复执行接下来的任务
  • Semaphore:
    • 用于多个共享资源的互斥(抢车位)
    • 用于并发线程数的控制
  • LockSupport: 用来创建锁和其他同步类的基本线程阻塞原语,它使用了一种许可证的概念来做到阻塞和唤
  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值