Skip to content

Commit adf246a

Browse files
author
finnjiang
committed
update
1 parent 2abb0bb commit adf246a

File tree

7 files changed

+494
-21
lines changed

7 files changed

+494
-21
lines changed

image/Java-24.png

269 KB
Loading

image/Java-25.png

118 KB
Loading

image/scene-5.png

59.6 KB
Loading

notes/java/Java基础.md

Lines changed: 254 additions & 7 deletions
Large diffs are not rendered by default.

notes/java/Java容器.md

Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
- [Java集合类框架图](#java%E9%9B%86%E5%90%88%E7%B1%BB%E6%A1%86%E6%9E%B6%E5%9B%BE)
66
- [Java集合类框架的基本接口有哪些?](#java%E9%9B%86%E5%90%88%E7%B1%BB%E6%A1%86%E6%9E%B6%E7%9A%84%E5%9F%BA%E6%9C%AC%E6%8E%A5%E5%8F%A3%E6%9C%89%E5%93%AA%E4%BA%9B)
77
- [HashSet和TreeSet区别](#hashset%E5%92%8Ctreeset%E5%8C%BA%E5%88%AB)
8+
- [ArrayList和LinkedList的区别](#arraylist%E5%92%8Clinkedlist%E7%9A%84%E5%8C%BA%E5%88%AB)
89
- [讲一下LinkedHashMap](#%E8%AE%B2%E4%B8%80%E4%B8%8Blinkedhashmap)
910
- [Java8 中HashMap的优化(引入红黑树的数据结构和扩容的优化)](#java8-%E4%B8%ADhashmap%E7%9A%84%E4%BC%98%E5%8C%96%E5%BC%95%E5%85%A5%E7%BA%A2%E9%BB%91%E6%A0%91%E7%9A%84%E6%95%B0%E6%8D%AE%E7%BB%93%E6%9E%84%E5%92%8C%E6%89%A9%E5%AE%B9%E7%9A%84%E4%BC%98%E5%8C%96)
1011
- [Map遍历的keySet()和entrySet()性能差异原因](#map%E9%81%8D%E5%8E%86%E7%9A%84keyset%E5%92%8Centryset%E6%80%A7%E8%83%BD%E5%B7%AE%E5%BC%82%E5%8E%9F%E5%9B%A0)
@@ -15,10 +16,12 @@
1516
- [HashMap原理](#hashmap%E5%8E%9F%E7%90%86)
1617
- [HashMap特性](#hashmap%E7%89%B9%E6%80%A7)
1718
- [HashMap的原理,内部数据结构](#hashmap%E7%9A%84%E5%8E%9F%E7%90%86%E5%86%85%E9%83%A8%E6%95%B0%E6%8D%AE%E7%BB%93%E6%9E%84)
19+
- [HashMap的hash函数原理](#hashmap%E7%9A%84hash%E5%87%BD%E6%95%B0%E5%8E%9F%E7%90%86)
1820
- [讲一下 HashMap 中 put 方法过程](#%E8%AE%B2%E4%B8%80%E4%B8%8B-hashmap-%E4%B8%AD-put-%E6%96%B9%E6%B3%95%E8%BF%87%E7%A8%8B)
1921
- [get()方法的工作原理](#get%E6%96%B9%E6%B3%95%E7%9A%84%E5%B7%A5%E4%BD%9C%E5%8E%9F%E7%90%86)
2022
- [HashMap的put()方法流程](#hashmap%E7%9A%84put%E6%96%B9%E6%B3%95%E6%B5%81%E7%A8%8B)
2123
- [HashMap中hash函数怎么是是实现的?还有哪些 hash 的实现方式?](#hashmap%E4%B8%ADhash%E5%87%BD%E6%95%B0%E6%80%8E%E4%B9%88%E6%98%AF%E6%98%AF%E5%AE%9E%E7%8E%B0%E7%9A%84%E8%BF%98%E6%9C%89%E5%93%AA%E4%BA%9B-hash-%E7%9A%84%E5%AE%9E%E7%8E%B0%E6%96%B9%E5%BC%8F)
24+
- [Java8 HashMap扩容时为什么不需要重新hash?](#java8-hashmap%E6%89%A9%E5%AE%B9%E6%97%B6%E4%B8%BA%E4%BB%80%E4%B9%88%E4%B8%8D%E9%9C%80%E8%A6%81%E9%87%8D%E6%96%B0hash)
2225
- [HashMap 怎样解决冲突?](#hashmap-%E6%80%8E%E6%A0%B7%E8%A7%A3%E5%86%B3%E5%86%B2%E7%AA%81)
2326
- [扩展问题1:当两个对象的hashcode相同会发生什么?](#%E6%89%A9%E5%B1%95%E9%97%AE%E9%A2%981%E5%BD%93%E4%B8%A4%E4%B8%AA%E5%AF%B9%E8%B1%A1%E7%9A%84hashcode%E7%9B%B8%E5%90%8C%E4%BC%9A%E5%8F%91%E7%94%9F%E4%BB%80%E4%B9%88)
2427
- [扩展问题2:抛开 HashMap,hash 冲突有那些解决办法?](#%E6%89%A9%E5%B1%95%E9%97%AE%E9%A2%982%E6%8A%9B%E5%BC%80-hashmaphash-%E5%86%B2%E7%AA%81%E6%9C%89%E9%82%A3%E4%BA%9B%E8%A7%A3%E5%86%B3%E5%8A%9E%E6%B3%95)
@@ -53,6 +56,12 @@
5356
1. TreeSet是SortedSet接口的唯一实现类
5457
2. TreeSet可以确保集合元素处于排序状态。TreeSet支持两种排序方式,自然排序 和定制排序,其中自然排序为默认的排序方式。向TreeSet中加入的应该是同一个类的对象
5558

59+
## ArrayList和LinkedList的区别
60+
- **底层实现**:ArrayList是实现了基于动态数组的数据结构,而LinkedList是基于链表的数据结构,ArrayList需要扩容、LinkedList不需要
61+
- **时间复杂度**:对于随机访问get和set,ArrayList要优于LinkedList,因为LinkedList要移动指针
62+
- **使用场景**:LinkedList是个双向链表,它同样可以被当作栈、队列或双端队列来使用。
63+
64+
5665

5766

5867
## 讲一下LinkedHashMap
@@ -226,6 +235,17 @@ HashMap是基于hashing的原理,底层使用哈希表(数组 + 链表)实
226235

227236
存储对象时,我们将K/V传给put方法时,它调用hashCode计算hash从而得到bucket位置,进一步存储,HashMap会根据当前bucket的占用情况自动调整容量(超过Load Facotr则resize为原来的2倍)。获取对象时,我们将K传给get,它调用hashCode计算hash从而得到bucket位置,并进一步调用equals()方法确定键值对。如果发生碰撞的时候,Hashmap通过链表将产生碰撞冲突的元素组织起来,在Java 8中,如果一个bucket中碰撞冲突的元素超过某个限制(默认是8),则使用红黑树来替换链表,从而提高速度。
228237

238+
### HashMap的hash函数原理
239+
```
240+
static final int hash(Object key) {
241+
int h;
242+
return (key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16);
243+
}
244+
```
245+
Java 8中这一步做了优化,只做一次16位右位移异或混合,而不是四次,但原理是不变的。
246+
247+
优化了高位运算的算法,**通过hashCode()的高16位异或低16位实现的**,主要是从速度、功效、质量来考虑的
248+
229249
### 讲一下 HashMap 中 put 方法过程
230250
1. 对key的hashCode做hash操作,然后再计算在bucket中的index(1.5 HashMap的哈希函数);
231251
2. 如果没碰撞直接放到bucket里;
@@ -244,6 +264,41 @@ HashMap是基于hashing的原理,底层使用哈希表(数组 + 链表)实
244264
2. h & (length-1); //通过位操作得到下标index。
245265

246266
还有数字分析法、平方取中法、分段叠加法、 除留余数法、 伪随机数法。
267+
### Java8 HashMap扩容时为什么不需要重新hash?
268+
```
269+
if ((e.hash & oldCap) == 0) {
270+
if (loTail == null)
271+
loHead = e;
272+
else
273+
loTail.next = e;
274+
loTail = e;
275+
}
276+
else {
277+
if (hiTail == null)
278+
hiHead = e;
279+
else
280+
hiTail.next = e;
281+
hiTail = e;
282+
}
283+
284+
```
285+
可以看到它是通过将数据的hash与扩容前的长度进行与操作,根据`e.hash & oldCap`的结果来判断,如果是0,说明位置没有发生变化,如果不为0,说明位置发生了变化,而且新的位置=老的位置+老的数组长度。
286+
287+
288+
比如数据B它经过hash之后的值为 1111,在扩容之前数组长度是8,数据B的位置是:
289+
```
290+
(n-1)&hash = (8-1) & 1111 = 111 & 1111 = 0111
291+
```
292+
扩容之后,数组长度是16,重新计算hash位置是:
293+
```
294+
(n-1)&hash = (16-1) & 1111 = 1111 & 1111 = 1111
295+
```
296+
可见数据B的位置发生了变化,同时新的位置和原来的位置关系是:
297+
**新的位置(1111)= 1000+原来的位置(0111)=原来的长度(8)+原来的位置(0111)**
298+
继续看一下e.hash & oldCap的结果
299+
```
300+
e.hash & oldCap = 1111 & 8 = 1111 & 1000 = 1000 (!=0)
301+
```
247302

248303
### HashMap 怎样解决冲突?
249304
HashMap中处理冲突的方法实际就是链地址法,内部数据结构是数组+单链表。

notes/java/Java并发.md

Lines changed: 46 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -8,10 +8,15 @@
88
- [线程为什么调用start()而不是直接调用run()](#%E7%BA%BF%E7%A8%8B%E4%B8%BA%E4%BB%80%E4%B9%88%E8%B0%83%E7%94%A8start%E8%80%8C%E4%B8%8D%E6%98%AF%E7%9B%B4%E6%8E%A5%E8%B0%83%E7%94%A8run)
99
- [阻塞,等待,挂起,休眠的区别](#%E9%98%BB%E5%A1%9E%E7%AD%89%E5%BE%85%E6%8C%82%E8%B5%B7%E4%BC%91%E7%9C%A0%E7%9A%84%E5%8C%BA%E5%88%AB)
1010
- [多线程上下文切换的影响](#%E5%A4%9A%E7%BA%BF%E7%A8%8B%E4%B8%8A%E4%B8%8B%E6%96%87%E5%88%87%E6%8D%A2%E7%9A%84%E5%BD%B1%E5%93%8D)
11-
- [synchronized 的底层怎么实现](#synchronized-%E7%9A%84%E5%BA%95%E5%B1%82%E6%80%8E%E4%B9%88%E5%AE%9E%E7%8E%B0)
11+
- [synchronized](#synchronized)
12+
- [synchronized 的底层怎么实现](#synchronized-%E7%9A%84%E5%BA%95%E5%B1%82%E6%80%8E%E4%B9%88%E5%AE%9E%E7%8E%B0)
13+
- [为什么notify和wait方法必须在synchronized方法中使用?](#%E4%B8%BA%E4%BB%80%E4%B9%88notify%E5%92%8Cwait%E6%96%B9%E6%B3%95%E5%BF%85%E9%A1%BB%E5%9C%A8synchronized%E6%96%B9%E6%B3%95%E4%B8%AD%E4%BD%BF%E7%94%A8)
14+
- [1、依赖锁对象的监视器monitor](#1%E4%BE%9D%E8%B5%96%E9%94%81%E5%AF%B9%E8%B1%A1%E7%9A%84%E7%9B%91%E8%A7%86%E5%99%A8monitor)
15+
- [2、避免lost wake up问题](#2%E9%81%BF%E5%85%8Dlost-wake-up%E9%97%AE%E9%A2%98)
1216
- [jdk1.6以后对synchronized锁做了哪些优化](#jdk16%E4%BB%A5%E5%90%8E%E5%AF%B9synchronized%E9%94%81%E5%81%9A%E4%BA%86%E5%93%AA%E4%BA%9B%E4%BC%98%E5%8C%96)
1317
- [Java有哪些锁?](#java%E6%9C%89%E5%93%AA%E4%BA%9B%E9%94%81)
14-
- [讲一下CAS](#%E8%AE%B2%E4%B8%80%E4%B8%8Bcas)
18+
- [CAS](#cas)
19+
- [CAS 介绍](#cas-%E4%BB%8B%E7%BB%8D)
1520
- [CAS到底最后加没加锁](#cas%E5%88%B0%E5%BA%95%E6%9C%80%E5%90%8E%E5%8A%A0%E6%B2%A1%E5%8A%A0%E9%94%81)
1621
- [CountDownLatch与CyclicBarrier的比较](#countdownlatch%E4%B8%8Ecyclicbarrier%E7%9A%84%E6%AF%94%E8%BE%83)
1722
- [源码上的区别](#%E6%BA%90%E7%A0%81%E4%B8%8A%E7%9A%84%E5%8C%BA%E5%88%AB)
@@ -39,9 +44,14 @@
3944
- [写final域的重排序规则](#%E5%86%99final%E5%9F%9F%E7%9A%84%E9%87%8D%E6%8E%92%E5%BA%8F%E8%A7%84%E5%88%99)
4045
- [读final域的重排序规则](#%E8%AF%BBfinal%E5%9F%9F%E7%9A%84%E9%87%8D%E6%8E%92%E5%BA%8F%E8%A7%84%E5%88%99)
4146
- [notify和notifyAll的区别](#notify%E5%92%8Cnotifyall%E7%9A%84%E5%8C%BA%E5%88%AB)
42-
- [ConcurrentHashMap是如何在保证并发安全的同时提高性能](#concurrenthashmap%E6%98%AF%E5%A6%82%E4%BD%95%E5%9C%A8%E4%BF%9D%E8%AF%81%E5%B9%B6%E5%8F%91%E5%AE%89%E5%85%A8%E7%9A%84%E5%90%8C%E6%97%B6%E6%8F%90%E9%AB%98%E6%80%A7%E8%83%BD)
43-
- [为什么java.util.concurrent 包里没有并发的ArrayList实现?](#%E4%B8%BA%E4%BB%80%E4%B9%88javautilconcurrent-%E5%8C%85%E9%87%8C%E6%B2%A1%E6%9C%89%E5%B9%B6%E5%8F%91%E7%9A%84arraylist%E5%AE%9E%E7%8E%B0)
44-
- [比AtomicLong更高性能的LongAdder](#%E6%AF%94atomiclong%E6%9B%B4%E9%AB%98%E6%80%A7%E8%83%BD%E7%9A%84longadder)
47+
- [JUC](#juc)
48+
- [ConcurrentHashMap是如何在保证并发安全的同时提高性能](#concurrenthashmap%E6%98%AF%E5%A6%82%E4%BD%95%E5%9C%A8%E4%BF%9D%E8%AF%81%E5%B9%B6%E5%8F%91%E5%AE%89%E5%85%A8%E7%9A%84%E5%90%8C%E6%97%B6%E6%8F%90%E9%AB%98%E6%80%A7%E8%83%BD)
49+
- [为什么java.util.concurrent 包里没有并发的ArrayList实现?](#%E4%B8%BA%E4%BB%80%E4%B9%88javautilconcurrent-%E5%8C%85%E9%87%8C%E6%B2%A1%E6%9C%89%E5%B9%B6%E5%8F%91%E7%9A%84arraylist%E5%AE%9E%E7%8E%B0)
50+
- [ConcurrentModificationException异常出现的原因](#concurrentmodificationexception%E5%BC%82%E5%B8%B8%E5%87%BA%E7%8E%B0%E7%9A%84%E5%8E%9F%E5%9B%A0)
51+
- [fail-fast机制](#fail-fast%E6%9C%BA%E5%88%B6)
52+
- [1、在单线程环境下的解决办法](#1%E5%9C%A8%E5%8D%95%E7%BA%BF%E7%A8%8B%E7%8E%AF%E5%A2%83%E4%B8%8B%E7%9A%84%E8%A7%A3%E5%86%B3%E5%8A%9E%E6%B3%95)
53+
- [2、在多线程环境下的解决方法](#2%E5%9C%A8%E5%A4%9A%E7%BA%BF%E7%A8%8B%E7%8E%AF%E5%A2%83%E4%B8%8B%E7%9A%84%E8%A7%A3%E5%86%B3%E6%96%B9%E6%B3%95)
54+
- [比AtomicLong更高性能的LongAdder](#%E6%AF%94atomiclong%E6%9B%B4%E9%AB%98%E6%80%A7%E8%83%BD%E7%9A%84longadder)
4555
- [两个线程同时执行i++100次,结果是多少](#%E4%B8%A4%E4%B8%AA%E7%BA%BF%E7%A8%8B%E5%90%8C%E6%97%B6%E6%89%A7%E8%A1%8Ci100%E6%AC%A1%E7%BB%93%E6%9E%9C%E6%98%AF%E5%A4%9A%E5%B0%91)
4656
- [如何排查死锁?](#%E5%A6%82%E4%BD%95%E6%8E%92%E6%9F%A5%E6%AD%BB%E9%94%81)
4757
- [线程池](#%E7%BA%BF%E7%A8%8B%E6%B1%A0)
@@ -144,8 +154,8 @@ start方法其实是在一个新的操作系统线程上面去调用run方法。
144154
3. 使用最少线程。避免创建不需要的线程,比如任务很少,但是创建了很多线程来处理,这样会造成大量线程都处于等待状态
145155
4. 协程。在单线程里实现多任务的调度,并在单线程里维持多个任务间的切换
146156

147-
148-
## synchronized 的底层怎么实现
157+
## synchronized
158+
### synchronized 的底层怎么实现
149159
1. **同步代码块**(Synchronization)基于进入和退出管程(Monitor)对象实现。每个对象有一个监视器锁(monitor)。当monitor被占用时就会处于锁定状态,线程执行monitorenter指令时尝试获取monitor的所有权,过程如下:
150160

151161
- 如果monitor的进入数为0,则该线程进入monitor,然后将进入数设置为1,该线程即为monitor的所有者。
@@ -155,6 +165,15 @@ start方法其实是在一个新的操作系统线程上面去调用run方法。
155165
- 如果其他线程已经占用了monitor,则该线程进入阻塞状态,直到monitor的进入数为0,再重新尝试获取monitor的所有权。
156166
2. **被 synchronized 修饰的同步方法**并没有通过指令monitorenter和monitorexit来完成(理论上其实也可以通过这两条指令来实现),不过相对于普通方法,其常量池中多了ACC_SYNCHRONIZED标示符。JVM就是根据该标示符来实现方法的同步的:当方法调用时,调用指令将会检查方法的 ACC_SYNCHRONIZED 访问标志是否被设置,如果设置了,执行线程将先获取monitor,获取成功之后才能执行方法体,方法执行完后再释放monitor。在方法执行期间,其他任何线程都无法再获得同一个monitor对象。 其实本质上没有区别,只是方法的同步是一种隐式的方式来实现,无需通过字节码来完成
157167

168+
### 为什么notify和wait方法必须在synchronized方法中使用?
169+
#### 1、依赖锁对象的监视器monitor
170+
这是因为调用这三个方法之前必须拿要到当前锁对象的监视器monitor对象,也就是说notify/notifyAll和wait方法依赖于monitor对象,又因为monitor存在于对象头的Mark Word中(存储monitor引用指针),而synchronized关键字可以获取monitor ,所以,notify/notifyAll和wait方法必须在synchronized代码块或者synchronized方法中调用。
171+
172+
#### 2、避免lost wake up问题
173+
因为会导致lost wake up问题,说白了就唤不醒消费者
174+
![](https://github.com/zaiyunduan123/Java-Interview/blob/master/image/Java-24.png)
175+
176+
为了避免出现这种lost wake up问题,Java强制我们的wait()/notify()调用必须要在一个同步块中。
158177

159178

160179
### jdk1.6以后对synchronized锁做了哪些优化
@@ -198,9 +217,9 @@ start方法其实是在一个新的操作系统线程上面去调用run方法。
198217
- 自旋锁
199218

200219

220+
## CAS
201221

202-
203-
## 讲一下CAS
222+
### CAS 介绍
204223
CAS,compare and swap的缩写,中文翻译成比较并交换。乐观锁用到的机制就是CAS,每次不加锁而是假设没有冲突而去完成某项操作,如果因为冲突失败就重试。
205224

206225
原理:
@@ -539,17 +558,16 @@ reader() 方法包含三个操作:
539558
代码块,它会释放掉该对象锁,这时锁池中的线程会继续竞争该对象锁。
540559
- 尽量使用 notifyAll(),notify()可能会导致死锁
541560

542-
## ConcurrentHashMap是如何在保证并发安全的同时提高性能
561+
## JUC
562+
### ConcurrentHashMap是如何在保证并发安全的同时提高性能
543563
其实就是要控制锁的粒度,尽量避免锁的发生
544564

545565
ConcurrentHashMap使用了一些技巧来获取高的并发性能,同时避免了锁。这些技巧包括:
546566
1. 使用CAS乐观锁和volatile代替RentrantLock
547567
2. spread二次哈希进行segment分段。
548568
3. stream提高并行处理能力。
549569

550-
551-
552-
## 为什么java.util.concurrent 包里没有并发的ArrayList实现?
570+
### 为什么java.util.concurrent 包里没有并发的ArrayList实现?
553571

554572
我认为在java.util.concurrent包中没有加入并发的ArrayList实现的主要原因是:**很难去开发一个通用并且没有并发瓶颈的线程安全的List。**
555573

@@ -561,8 +579,22 @@ ConcurrentHashMap使用了一些技巧来获取高的并发性能,同时避免
561579

562580
CopyOnWriteArrayList是一个有趣的例子,它规避了只读操作(如get/contains)并发的瓶颈,但是它为了做到这点,在修改操作中做了很多工作和修改可见性规则。 此外,修改操作还会锁住整个List,因此这也是一个并发瓶颈。所以从理论上来说,CopyOnWriteArrayList并不算是一个通用的并发List。
563581

582+
### ConcurrentModificationException异常出现的原因
583+
原因:如果modCount不等于expectedModCount,则抛出ConcurrentModificationException异常。
584+
关键点就在于:调用list.remove()方法导致modCount和expectedModCount的值不一致。
585+
586+
### fail-fast机制
587+
这种机制经常出现在多线程环境下 , 当前线程会维护一个计数比较器, 即 expectedModCount, 记录已经修改的次数。在进入遍历前, 会把实时修改次数 modCount 赋值给 expectedModCount,如果这两个数据不相等 , 则抛出异常。
588+
589+
Iterator、COW(Copy-on-write)是 fail-safe机制的
590+
591+
#### 1、在单线程环境下的解决办法
592+
使用iterator删除,并且调用iterator的remove方法,不是list的remove方法
593+
#### 2、在多线程环境下的解决方法
594+
1、在使用iterator迭代的时候使用synchronized或者Lock进行同步;
595+
2、使用并发容器CopyOnWriteArrayList代替ArrayList和Vector。
564596

565-
## 比AtomicLong更高性能的LongAdder
597+
### 比AtomicLong更高性能的LongAdder
566598
LongAdder在高并发的场景下会比它的前辈————AtomicLong 具有更好的性能,代价是消耗更多的内存空间
567599

568600
AtomicLong在并发量较低的环境下,线程冲突的概率比较小,自旋的次数不会很多。但是,高并发环境下,N个线程同时进行自旋操作,会出现大量失败并不断自旋的情况。

0 commit comments

Comments
 (0)