面试题-java基础面试16问

文章目录
  1. 1. java基础面试16问
    1. 1.1. 1.进程和线程的区别
    2. 1.2. 2.synchronized原理
    3. 1.3. 3.锁的优化机制
    4. 1.4. 4.对象头都包含那些内容
    5. 1.5. 5.reentrantlock原理,和syn区别
    6. 1.6. 6.cas原理
    7. 1.7. 7.cas问题
    8. 1.8. 8.hashmap原理
    9. 1.9. 9.多线程怎么用map?有了解concurrenthashmap吗
    10. 1.10. 10.volatile原理你知道吗
    11. 1.11. 11. jmm有什么意义
    12. 1.12. 12.工作内存和主内存是什么
    13. 1.13. 13.threadlocal是什么
    14. 1.14. 14.引用,强软弱虚
    15. 1.15. 15. 线程池的核心
    16. 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部分:

  1. 一些锁或gc信息:hash值,分代年龄,gc标志,偏向锁id,偏向锁时间戳,轻量级锁指针,重量级锁指针
  2. 代表的实例的指针,通过这个指针知道这个对象是什么类
  3. 如果是数组,还有数据长度

5.reentrantlock原理,和syn区别

原理就是依赖aqs。首先有个volatile变量state来记录加锁数量,然后记下目前的线程id,cas操作来加锁,加锁成功其他就进入【阻塞队列自旋】。直至该变量为0,把线程id=null,才【唤醒阻塞队列的线程】。

在jdk7,8。性能区别不大了。

区别:

  1. lock需要显示加锁,放锁。
  2. sync是关键字,Lock是api类
  3. lock可以中断锁,可以公平锁
  4. lock可以绑定condition来指定通知哪个线程。而syn的notify, notifyall是随机的。

【5.sync是悲观同步阻塞,lock是cas自旋阻塞

6.cas原理

compare and swap。

  1. 比较旧值和内存值是否一样。
  2. 一样就,新值放内存值。
  3. 不一样就,不换。

7.cas问题

  1. aba问题,就是虽然旧值和内存值一样,但可能中间已经经历过很多次换值了。所以可以用版本号来做比较
  2. 循环次数多耗资源。这个也是为什么轻量级锁升级为重量级锁,因为太多的循环还不如直接阻塞了。
  3. 多个值的话,要使用atomicReference或者sync来做

8.hashmap原理

由数据加链表组成。不是线程安全的。jdk7和jdk8区别主要在:

  1. 以前头插容易死循环,现在尾插
  2. 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. 线程池的核心

五个核心概念:

  1. core pool size
  2. max pool size
  3. blockqueue
  4. keep alive time
  5. reject handler

一开始先放core运行,放满了再放block queue,也满了再开max数量的线程来处理。处理完所有之后keep alive time之后回收多余的Max线程,恢复到core线程数。如果max开了也处理不完blockqueue,再有新的就会按reject handler来拒绝。

16. 拒绝策略有哪些

  1. abort抛出异常
  2. discardOldest抛弃最旧的
  3. discard直接扔掉
  4. 还有一个是谁创建线程谁执行,[callerruns]