Skip to content

Commit 0d6749d

Browse files
committed
Update Java Notes
1 parent 3f23615 commit 0d6749d

File tree

1 file changed

+66
-15
lines changed

1 file changed

+66
-15
lines changed

Java.md

Lines changed: 66 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -5331,7 +5331,7 @@ transient int size;
53315331

53325332
* `(n - 1) & hash`:计算下标位置
53335333

5334-
![](https://gitee.com/seazean/images/raw/master/Java/HashMap-putVal哈希运算.png)
5334+
<img src="https://gitee.com/seazean/images/raw/master/Java/HashMap-putVal哈希运算.png" style="zoom:80%;" />
53355335

53365336
总结: hashcode 转化为32位二进制,高16 bit和低16 bit做了一个异或
53375337

@@ -5442,17 +5442,37 @@ transient int size;
54425442

54435443
4. resize
54445444

5445-
当HashMap中的元素个数超过数组大小(数组长度)*loadFactor(负载因子)时,就会进行数组扩容。扩容会伴随着一次重新hash分配,并且会遍历hash表中所有的元素,非常耗时,所以要尽量避免resize
5446-
5447-
扩容时split方法会将树**拆成高位和低位两个链表**,判断长度是否小于等于6,小于则变成非树节点
5445+
当HashMap中的元素个数超过(数组长度)*loadFactor(负载因子)`时,就会进行数组扩容。扩容会伴随着一次重新hash分配,并且会遍历hash表中所有的元素,非常耗时,所以要尽量避免resize
54485446

54495447
HashMap在进行扩容时,使用的rehash方式非常巧妙,因为每次扩容都是翻倍,与原来计算的 (n-1)&hash的结果相比,只是多了一个bit位,节点**要么就在原来的位置,要么就被分配到"原位置+旧容量"的位置**
5450-
5451-
判断:当前数组长度n为1的位为 x,如果key的哈希值 x 位也为1,则扩容后的索引为 now + n
5452-
5448+
5449+
判断:e.hash与oldCap对应的有效高位上的值是1,即当前数组长度n为1的位为 x,如果key的哈希值 x 位也为1,则扩容后的索引为 now + n
5450+
54535451
注意:这里也要求**数组长度2的幂**
54545452

5455-
![](https://gitee.com/seazean/images/raw/master/Java/HashMap-resize扩容.png)
5453+
![](https://gitee.com/seazean/images/raw/master/Java/HashMap-resize扩容.png)
5454+
5455+
红黑树节点:扩容时split方法会将树**拆成高位和低位两个链表**,判断长度是否小于等于6
5456+
5457+
```java
5458+
//如果低位链表首节点不为null,说明有这个链表存在
5459+
if (loHead != null) {
5460+
//如果链表下的元素小于等于6
5461+
if (lc <= UNTREEIFY_THRESHOLD)
5462+
//那就从红黑树转链表了,低位链表,迁移到新数组中下标不变,还是等于原数组到下标
5463+
tab[index] = loHead.untreeify(map);
5464+
else {
5465+
//低位链表,迁移到新数组中下标不变,把低位链表整个赋值到这个下标下
5466+
tab[index] = loHead;
5467+
//如果高位首节点不为空,说明原来的红黑树已经被拆分成两个链表了
5468+
if (hiHead != null)
5469+
//需要构建新的红黑树了
5470+
loHead.treeify(tab);
5471+
}
5472+
}
5473+
```
5474+
5475+
54565476

54575477
4. remove
54585478
删除是首先先找到元素的位置,如果是链表就遍历链表找到元素之后删除。如果是用红黑树就遍历树然后找到之后做删除,树小于6的时候要转链表
@@ -14872,7 +14892,7 @@ public class Test14 {
1487214892
`public static boolean interrupted()`:判断当前线程是否被打断,清除打断标记
1487314893
`public boolean isInterrupted()`:判断当前线程是否被打断,不清除打断标记
1487414894

14875-
sleep,wait,join方法都会让线程进入阻塞 (Waiting) 状态,打断进程会**清空打断状态** (false)
14895+
sleep,wait,join方法都会让线程进入阻塞状态,打断进程会**清空打断状态** (false)
1487614896

1487714897
```java
1487814898
public static void main(String[] args) throws InterruptedException {
@@ -15780,6 +15800,8 @@ class BigRoom {
1578015800

1578115801
##### 死锁
1578215802

15803+
###### 形成
15804+
1578315805
死锁:多个线程同时被阻塞,它们中的一个或者全部都在等待某个资源被释放,由于线程被无限期地阻塞,因此程序不可能正常终止
1578415806

1578515807
java 死锁产生的四个必要条件:
@@ -15791,7 +15813,7 @@ java 死锁产生的四个必要条件:
1579115813
四个条件都成立的时候,便形成死锁。死锁情况下打破上述任何一个条件,便可让死锁消失。
1579215814

1579315815
```java
15794-
public class ThreadDead {
15816+
public class Dead {
1579515817
public static Object resources1 = new Object();
1579615818
public static Object resources2 = new Object();
1579715819
public static void main(String[] args) {
@@ -15858,7 +15880,9 @@ class HoldLockThread implements Runnable {
1585815880

1585915881

1586015882

15861-
**定位死锁**:
15883+
###### 定位
15884+
15885+
定位死锁的方法:
1586215886

1586315887
* 检测死锁可以使用 jconsole工具,或者使用 jps 定位进程 id,再用 `jstack id`定位死锁
1586415888

@@ -15898,7 +15922,7 @@ class HoldLockThread implements Runnable {
1589815922

1589915923
* linux 下可以通过 top 先定位到CPU 占用高的 Java 进程,再利用 `top -Hp 进程id` 来定位是哪个线程,最后再用 jstack 排查
1590015924

15901-
避免死锁:避免死锁要注意加锁顺序
15925+
* 避免死锁:避免死锁要注意加锁顺序
1590215926

1590315927

1590415928

@@ -16948,23 +16972,50 @@ final class Message {
1694816972

1694916973
### JMM
1695016974

16975+
#### 模型
16976+
1695116977
Java 内存模型是 Java MemoryModel(JMM),本身是一种**抽象的概念**,实际上并不存在,描述的是一组规则或规范,通过这组规范定义了程序中各个变量(包括实例字段,静态字段和构成数组对象的元素)的访问方式
1695216978

1695316979
JMM主要是为了规定了线程和内存之间的一些关系,根据JMM的设计,系统存在一个主内存(Main Memory),Java中所有变量都存储在主存中,对于所有线程都是共享的;每条线程都有自己的工作内存(Working Memory),工作内存中保存的是主存中某些**变量的拷贝**,线程对所有变量的操作都是先对变量进行拷贝,然后在工作内存中进行,不能直接操作主内存中的变量;线程之间无法相互直接访问,线程间的通信(传递)必须通过主内存来完成
1695416980

1695516981
![](https://gitee.com/seazean/images/raw/master/Java/JMM内存模型.png)
1695616982

16983+
主内存和工作内存:
16984+
16985+
* 主内存:计算机的内存,也就是经常提到的8G内存,16G内存
16986+
* 工作内存:多个线程同时访问变量时,每个线程都会拷贝一份到各自的工作内存
16987+
1695716988
**jvm和jmm之间的关系**:
1695816989

1695916990
* jmm中的主内存、工作内存与jvm中的Java堆、栈、方法区等并不是同一个层次的内存划分
1696016991
* 这两者基本上是没有关系的,如果两者一定要勉强对应起来,那从变量、主内存、工作内存的定义来看:
1696116992
* 主内存主要对应于Java堆中的对象实例数据部分,而工作内存则对应于虚拟机栈中的部分区域
1696216993
* 从更低层次上说,主内存就直接对应于物理硬件的内存,而为了获取更好的运行速度,虚拟机(甚至是硬件系统本身的优化措施)可能会让工作内存优先存储于寄存器和高速缓存中,因为程序运行时主要访问读写的是工作内存
1696316994

16964-
主内存和工作内存:
1696516995

16966-
* 主内存:计算机的内存,也就是经常提到的8G内存,16G内存
16967-
* 工作内存:多个线程同时访问变量时,每个线程都会拷贝一份到各自的工作内存
16996+
16997+
16998+
16999+
#### 交互
17000+
17001+
Java 内存模型定义了 8 个操作来完成主内存和工作内存的交互操作:
17002+
17003+
<img src="https://gitee.com/seazean/images/raw/master/Java/JMM内存交互.png" style="zoom: 67%;" />
17004+
17005+
* read:把一个变量的值从主内存传输到工作内存中
17006+
* load:在 read 之后执行,把 read 得到的值放入工作内存的变量副本中
17007+
* use:把工作内存中一个变量的值传递给执行引擎
17008+
* assign:把一个从执行引擎接收到的值赋给工作内存的变量
17009+
* store:把工作内存的一个变量的值传送到主内存中
17010+
* write:在 store 之后执行,把 store 得到的值放入主内存的变量中
17011+
* lock:作用于主内存的变量
17012+
* unlock
17013+
17014+
17015+
17016+
17017+
17018+
#### 特性
1696817019

1696917020
**JMM特性**:
1697017021

0 commit comments

Comments
 (0)