JAVA那点破事!并发、IO模型、集合、线程池、死锁、非阻塞、AQS....
扫描二维码
随时随地手机看文章
本文对一些高频问题做了汇总,为了便于大家查找问题,了解全貌,整理个目录,我们可以快速全局了解关于 JAVA
接下来,我们逐条来看看每个问题及答案
JDK、JRE、JVM 三者有什么关系?答案:
- JDK(全称 Java Development Kit),Java开发工具包,能独立创建、编译、运行程序。
JDK = JRE java开发工具(javac.exe/java.exe/jar.exe)
- JRE(全称 Java Runtime Environment),能运行已编译好的程序,但不能创建程序
JRE = JVM java核心类库
- JVM (全称 Java Virtual Machine),java虚拟机。
java创建对象有哪些方式?答案:
- 1、new 创建对象
- 2、反射机制创建对象
- 3、通过clone方法
- 4、序列化机制
== 和 equals 有什么区别?答案:
- == ,如果是基本数据类型,比较两个值是否相等;如果是对象,比较两个对象的引用是否相等,指向同一块内存区域
- equals,用于对象之间,比较两个对象的值是否相等。
hashCode()的作用?答案:生成哈希码,int类型,用于确定该对象在哈希表中的索引位置。每个类中都包含这个方法。
String、StringBuffer、StringBuilder 有什么区别?答案
- 1、String。采用
final
修饰,对象不可变,线程安全。如果对一个已经存在的String对象修改,会重新创建一个新对象,并把值放进去。 - 2、StringBuffer,采用
synchronized
关键字修饰,线程安全 - 3、StringBuilder,非线程安全,但效率会更高些,适用于单线程。
try-catch-finally,如catch中return了,还会执行finally吗?答案:当然啦,会在return之前执行。
进程和线程的区别?答案:
- 进程:是一个程序的执行流程,是系统进行资源分配和调度的基本单位,作用是程序能够并发执行提高资源利用率。因为进程的创建、销毁、切换产生大量的时间和空间的开销,所以进程的数量不能太多
- 线程:是比进程更小的能独立运行的基本单位,他是进程的一个实体,可以减少程序并发执行时的时间和空间开销,使得操作系统具有更好的并发性。多个线程可以共享进程的系统资源。线程基本不拥有系统资源,只有一些运行时必不可少的资源,比如程序计数器、寄存器和栈,进程则占有堆。
synchronized 的内部原理?答案:java提供的原子性内置锁,也被称为监视器锁。使用
synchronized
之后,会在编译之后在同步的代码块前后加上monitorenter
和monitorexit
字节码指令,依赖操作系统底层互斥锁实现。实现原子性操作和解决共享变量的内存可⻅性问题。内部处理过程(内部有两个队列waitSet和entryList。):- 1、当多个线程进入同步代码块时,首先进入
entryList
- 2、有一个线程获取到monitor锁后,就赋值给当前线程,并且计数器 1
- 3、如果线程调用wait方法,将释放锁,当前线程置为null,计数器-1,同时进入
waitSet
等待被唤醒,调用notify或者notifyAll之后又会进入entryList竞争锁 - 4、如果线程执行完毕,同样释放锁,计数器-1,当前线程置为null
synchronized 和 ReentrantLock 的区别?答案:
- ReentrantLock 实现了Lock接口。synchronized是系统关键字
- ReentrantLock需要手动指定锁范围。synchronized 支持同步块、同步方法
- 都具有可重入性
- 默认都是非公平锁。但 ReentrantLock 还支持公平模式,但性能会急剧下降
- ReentrantLock 需要显示的获取锁、释放锁
- ReentrantLock 支持多种方式获取锁。
- lock():阻塞模式来获取锁
- lockInterruptibly:阻塞式获取锁,支持中断
- tryLock():非阻塞模式尝试获取锁
- tryLock(long timeout, TimeUnit unit):同上,支持时间设置
- ReentrantLock 可以同时绑定多个Condition条件对象。
AQS (AbstractQueuedSynchronizer 抽象队列同步器 )的原理?答案:AQS内部维护一个
state状态位
,尝试加锁的时候通过CAS(CompareAndSwap)
修改值,如果成功设置为 1,并且把当前线程ID赋值,则代表加锁成功。一旦获取到锁,其他的线程将会被阻塞进入阻塞队列自旋,获得锁的线程释放锁的时候将会唤醒阻塞队列中的线程,释放锁的时候则会把state
重新置为0,同时当前线程ID
置为空。CAS 有什么缺点?答案:在多线程场景下,更新变量值被其他线程跑了个对冲,CAS会出现ABA问题。解决方式有很多,
- 可以通过,自增版本号方式,永远不会回退
- Java中提供了
AtomicStampedReference
,增加了标志字段,更新时不光检查值,还要检查当前的标志是否等于预期标志,全部满足条件才会更新 - 更多内容,CAS原理分析,解决银行转账ABA难题
Java 都用过哪些锁?答案:
- 乐观锁、悲观锁
- 分布式锁
- 独占锁、共享锁
- 互斥锁
- 读写锁
- 公平锁、非公平锁
- 可重入锁
- 自旋锁
- 分段锁
- 锁升级(无锁|偏向锁|轻量级锁|重量级锁)
- 锁优化技术(锁粗化、锁消除)
- 更多详细内容,一文全面梳理各种锁机制
HashMap原理?答案:内部由数组和链表组成,非线程安全。JDK1.7和1.8的主要区别在于头插和尾插方式的修改,头插容易导致HashMap链表死循环,并且1.8之后加入
红黑树
对性能有提升。- put插入:key 计算hash值,取模,找到数组位置,如果数组中没有元素直接存入,反之,则判断key是否相同,key相同就覆盖,否则就会插入到链表的尾部。如果链表的⻓度超过8且数据总量超过64,则会转换成
红黑树
。最后判断元素个数是否超过默认的⻓度(16)*负载因子(0.75),也就是12,超过则进行扩容。 - get查询:计算出hash值,然后去数组查询,是红黑树就去红黑树查,链表就遍历链表查询就可以了。
ConcurrentHashMap 如何能保证线程安全的?答案:ConcurrentHashmap在JDK1.7和1.8的版本改动比较大。
- 1.7 使用Segment HashEntry 分段锁的方式实现,
Segment
继承于ReentrantLock
,HashEntry
存储键值对数据。 - 1.8 采用数组 链表 红黑树。锁设计上抛弃了Segment分段锁,采用 CAS synchronized 实现。
ArrayList 和 LinkedList 有什么区别?答案:1、Arraylist
- 非线程安全
- 底层采用数组存储
- 插入、删除元素,时间复杂度受位置影响。默认是添加在列表的末尾,如果在位置 k 插入或删除一个元素,需要将k后面的元素后移或前移一位。
- 支持随机访问,根据索引下标序号,可以快速定位元素
- 需要连续的内存空间,中间不能有碎片
- 非线程安全
- 底层采用双向循环链表存储
- 插入、删除元素,时间复杂度不受位置影响,只需要更改位置 k的前后指针地址,时间复杂度为 O(1)
- 不支持高效的随机访问
- 不需要连续的内存空间
volatile 原理?答案:volatile声明的变量,值被更新后对其他线程立即可⻅。CPU会根据缓存一致性协议,强制线程重新从主内存加载最新的值到自己的工作内存中,而不是直接用cpu缓存中的值。
ThreadLocal 原理?答案:ThreadLocal有一个静态内部类
ThreadLocalMap
,ThreadLocalMap
又包含了一个Entry数组,Entry本身是一个弱引用,他的key是指向ThreadLocal的弱引用,Entry具备保存key -- value键值对的能力。在使用完之后调用remove方法
删除Entry对象,避免出现内存泄露。什么是工作内存、主内存?答案:
- 工作内存:寄存器、CPU缓存(L1、L2、L3)
- 主内存:主要是指物理内存
JUC并发包用过哪些线程安全的类?答案:
- ConcurrentHashMap
- CountDownLatch、CyclicBarrier
- Semaphore
- BlockingQueue
- ThreadPoolExecutor
- ReentrantLock、ReentrantReadWriteLock
- CompletableFuture
ThreadPoolExecutor 有哪些构造参数?答案:核心线程数、最大线程数、最大空闲时间、时间单位、任务队列、线程工厂、拒绝策略
- 更多内容,参考 史上最全ThreadPoolExecutor梳理(上篇)
- 更多内容,参考 史上最全ThreadPoolExecutor梳理(下篇)
ThreadPoolExecutor 的拒绝策略有哪些?答案:
- 1、AbortPolicy:直接丢弃任务,抛出异常,这是默认策略
- 2、CallerRunsPolicy:只用调用者所在的线程来处理任务
- 3、DiscardOldestPolicy:丢弃等待队列中最旧的任务,并执行当前任务
- 4、DiscardPolicy:直接丢弃任务,也不抛出异常
- 5、使用
RejectedExecutionHandler
接口,自定义实现
线程有哪些状态?是如何转换?答案:New、Runnable、Running、Blocked、Waiting、Timed Waiting、Terminated
IO 模型有哪五种?答案:1、阻塞IO。当 应用B 发起读取数据申请时,如果内核数据没有准备好,应用B会一直处于等待数据状态,直到内核把数据准备好了交给应用B才结束。2、非阻塞IO。当应用B发起读取数据申请时,如果内核数据没有准备好会即刻告诉应用B,不会让B在这里等待。3、IO复用模型。进程通过将一个或多个fd传递给select,阻塞在select操作上,select帮我们侦测多个fd是否准备就绪,当有fd准备就绪时,select返回数据可读状态,应用程序再调用recvfrom读取数据。4、信号IO。信号驱动IO不是用循环请求询问的方式去监控数据就绪状态,而是在调用sigaction时候建立一个SIGIO的信号联系,当内核数据准备好之后再通过SIGIO信号通知线程数据准备好后的可读状态,当线程收到可读状态的信号后,此时再向内核发起recvfrom读取数据的请求,因为信号驱动IO的模型下应用线程在发出信号监控后即可返回,不会阻塞,所以这样的方式下,一个应用线程也可以同时监控多个fd。5、异步IO。解决了应用程序需要先后
查看数据是否就绪
、发送接收数据请求
两个阶段的模式,在异步IO的模式下,只需要向内核发送一次请求就可以完成状态查询和数据拷贝的所有操作。阻塞IO 和 非阻塞IO 的区别?答案:如果数据没有就绪,在
查看数据是否就绪
的这个阶段是一直等待?还是直接返回一个标志信息。