Skip to content

Commit adee67f

Browse files
committed
Update Java Notes
1 parent 9112bff commit adee67f

File tree

4 files changed

+104
-79
lines changed

4 files changed

+104
-79
lines changed

DB.md

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2680,7 +2680,7 @@ CREATE TABLE us_pro(
26802680
事务的隔离级别相关概述:
26812681

26822682
* 事务的隔离级别
2683-
多个客户端操作时 ,各个客户端的事务之间应该是隔离的,相互独立的 , 不受影响的。而如果多个事务操作同一批数据时,则需要设置不同的隔离级别 , 否则就会产生问题 。
2683+
多个客户端操作时各个客户端的事务之间应该是隔离的,**不同的事务之间不该互相影响**。而如果多个事务操作同一批数据时,则需要设置不同的隔离级别 , 否则就会产生问题 。
26842684

26852685
* 隔离级别分类
26862686

@@ -2696,8 +2696,8 @@ CREATE TABLE us_pro(
26962696
| 问题 | 现象 |
26972697
| ---------- | ------------------------------------------------------------ |
26982698
| 脏读 | 是指在一个事务处理过程中读取了另一个未提交的事务中的数据 , 导致两次查询结果不一致 |
2699-
| 不可重复读 | 是指在一个事务处理过程中读取了另一个事务中修改并已提交的数据, 导致两次查询结果不一致 |
2700-
| 幻读 | 读取过程中数据条目发生了变化,查询某数据不存在,准备插入此记录,但执行插入时发现此记录已存在,无法插入或查询某数据不存在,执行delete删除,却发现删除成功 |
2699+
| 不可重复读 | 是指在一个事务处理过程中读取了另一个事务中修改并已提交的数据导致两次查询结果不一致 |
2700+
| 幻读 | 读取过程中数据条目发生了变化,查询某数据不存在,准备插入此记录,但执行插入时发现此记录已存在,无法插入或查询某数据不存在,执行delete删除,却发现删除成功 |
27012701

27022702

27032703

Java.md

Lines changed: 79 additions & 67 deletions
Original file line numberDiff line numberDiff line change
@@ -115,7 +115,7 @@ G-->H[double]
115115

116116
* float 与 double:
117117

118-
Java 不能隐式执行**向下转型**,因为这会使得精度降低
118+
Java 不能隐式执行**向下转型**,因为这会使得精度降低(参考多态)
119119

120120
```java
121121
//1.1字面量属于double类型,不能直接将1.1直接赋值给 float 变量,因为这是向下转型
@@ -3180,14 +3180,15 @@ public class MyArraysDemo {
31803180

31813181
包:java.util.Date。
31823182
构造器:
3183-
`public Date()`:创建当前系统的此刻日期时间对象。
3184-
`public Date(long time)`:把时间毫秒值转换成日期对象。
3183+
`public Date()`创建当前系统的此刻日期时间对象。
3184+
`public Date(long time)`把时间毫秒值转换成日期对象。
31853185
方法:
3186-
`public long getTime()`:返回自 1970 年 1 月 1 日 00:00:00 GMT 以来总的毫秒数。
3186+
`public long getTime()`返回自 1970 年 1 月 1 日 00:00:00 GMT 以来总的毫秒数。
31873187

31883188
时间记录的两种方式:
3189-
1.Date日期对象。
3190-
2.时间毫秒值:从1970-01-01 00:00:00开始走到此刻的总的毫秒值。 1s = 1000ms
3189+
3190+
1. Date日期对象
3191+
2. 时间毫秒值:从1970-01-01 00:00:00开始走到此刻的总的毫秒值。 1s = 1000ms
31913192

31923193
```java
31933194
public class DateDemo {
@@ -13193,7 +13194,7 @@ public class Demo3_6_1 {
1319313194
info = "abc"; //Python
1319413195
```
1319513196

13196-
虚方法表:在面向对象编程中,会频繁使用到动态分派,如果在每次动态分派的过程中都要重新在类的方法元数据中搜索合适目标就会影响到执行效率。为了提高性能,JVM采用在类的方法区建立一个虚方法表(virtual method table)来实现,使用索引表来代替查找,每个类中都有一个虚方法表,表中存放着各个方法的实际入口
13197+
虚方法表:在面向对象编程中,会频繁使用到**动态分派**,如果在每次动态分派的过程中都要重新在类的方法元数据中搜索合适目标就会影响到执行效率。为了提高性能,JVM采用在类的方法区建立一个虚方法表(virtual method table)来实现,使用索引表来代替查找,每个类中都有一个虚方法表,表中存放着各个方法的实际入口
1319713198

1319813199
![](https://gitee.com/seazean/images/raw/master/Java/JVM-方法调用动态分配.png)
1319913200

@@ -14752,7 +14753,7 @@ public class GeneratedMethodAccessor1 extends MethodAccessorImpl {
1475214753

1475314754
对于一个应用来说通常重点关注的性能指标主要是吞吐量、响应时间、QPS、TPS等、并发用户数等,而这些性能指标又依赖于系统服务器的资源,如:CPU、内存、磁盘IO、网络IO等。对于这些指标数据的收集,通常可以根据Java本身的工具或指令进行查询
1475414755

14755-
JDK 自带了监控工具,位于 JDK 的 bin 目录下,其中最常用的是 jconsole 和 jvisualvm 这两款视图监控工具:
14756+
JDK 自带了**监控工具,位于 JDK 的 bin 目录下**,其中最常用的是 jconsole 和 jvisualvm 这两款视图监控工具:
1475614757

1475714758
* jconsole:用于对 JVM 中的内存、线程和类等进行监控;
1475814759
* jvisualvm:JDK 自带的全能分析工具,可以分析:内存快照、线程快照、程序死锁、监控内存的变化、gc 变化等
@@ -15063,8 +15064,9 @@ Thread类API:
1506315064

1506415065
#### run start
1506515066

15066-
直接调用 run 是在主线程中执行了 run,没有启动新的线程
15067-
使用 start 是启动新的线程,通过新的线程间接执行 run 中的代码
15067+
run:称为线程体,包含了要执行的这个线程的内容,方法运行结束,此线程随即终止。直接调用 run 是在主线程中执行了 run,没有启动新的线程,需要顺序执行
15068+
15069+
start:使用 start 是启动新的线程,此线程处于就绪(可运行)状态,通过新的线程间接执行 run 中的代码
1506815070

1506915071
说明:**线程控制资源类**
1507015072

@@ -15714,6 +15716,7 @@ Mark Word 中就被设置指向 Monitor 对象的指针,这就是重量级锁
1571415716
* 开始时 Monitor 中 Owner 为 null
1571515717
* 当 Thread-2 执行 synchronized(obj) 就会将 Monitor 的所有者 Owner 置为 Thread-2,Monitor中只能有一
1571615718
个 Owner,**obj对象的Mark Word指向Monitor**
15719+
<img src="https://gitee.com/seazean/images/raw/master/Java/JUC-Monitor工作原理1.png" style="zoom:67%;" />
1571715720
* 在 Thread-2 上锁的过程中,Thread-3、Thread-4、Thread-5也来执行 synchronized(obj),就会进入
1571815721
EntryList BLOCKED
1571915722
* Thread-2 执行完同步代码块的内容,然后唤醒 EntryList 中等待的线程来竞争锁,竞争是**非公平的**
@@ -15748,7 +15751,7 @@ public static void main(String[] args) {
1574815751
```java
1574915752
0: new #2 // new Object
1575015753
3: dup
15751-
4: invokespecial #1 // invokespecial <init>:()V
15754+
4: invokespecial #1 // invokespecial <init>:()V,非虚方法
1575215755
7: astore_1 // lock引用 -> lock
1575315756
8: aload_1 // lock (synchronized开始)
1575415757
9: dup //一份用来初始化,一份用来引用
@@ -15911,7 +15914,7 @@ public class SpinLock {
1591115914
new Thread(() -> {
1591215915
//占有锁
1591315916
lock.lock();
15914-
Thread.sleep(5000);
15917+
Thread.sleep(10000);
1591515918

1591615919
//释放锁
1591715920
lock.unlock();
@@ -15968,7 +15971,7 @@ public class SpinLock {
1596815971
* 如果开启了偏向锁(默认开启),那么对象创建后,markword 值为 0x05 即最后 3 位为 101,这时它的
1596915972
thread、epoch、age 都为 0
1597015973
* 偏向锁是默认是延迟的,不会在程序启动时立即生效,如果想避免延迟,可以加VM参数 `-XX:BiasedLockingStartupDelay=0` 来禁用延迟
15971-
* 如果禁用了偏向锁,那么对象创建后,markword 值为 0x01 即最后 3 位为 001,这时它的 hashcode、age 都为 0,第一次用到 hashcode 时才会赋值,添加 VM 参数 `-XX:-UseBiasedLocking` 禁用偏向锁
15974+
* 如果禁用了偏向锁,那么对象创建后,markword 值为 0x01 即最后 3 位为 001,这时它的 hashcode、age 都为 0,**第一次用到 hashcode 时才会赋值**,添加 VM 参数 `-XX:-UseBiasedLocking` 禁用偏向锁
1597215975

1597315976

1597415977

@@ -16002,37 +16005,39 @@ public class SpinLock {
1600216005

1600316006
锁消除主要是通过逃逸分析来支持,如果堆上的共享数据不可能逃逸出去被其它线程访问到,那么就可以把它们当成私有数据对待,也就可以将它们的锁进行消除(同步消除:JVM内存分配)
1600416007

16005-
一些看起来没有加锁的代码,其实隐式的加了很多锁:
1600616008

16007-
```java
16008-
public static String concatString(String s1, String s2, String s3) {
16009-
return s1 + s2 + s3;
16010-
}
16011-
```
1601216009

16013-
String 是一个不可变的类,编译器会对 String 的拼接自动优化。在 JDK 1.5 之前,转化为StringBuffer对象的连续 append() 操作,每个append() 方法中都有一个同步块
16010+
***
16011+
1601416012

16015-
```java
16016-
public static String concatString(String s1, String s2, String s3) {
16017-
StringBuffer sb = new StringBuffer();
16018-
sb.append(s1);
16019-
sb.append(s2);
16020-
sb.append(s3);
16021-
return sb.toString();
16022-
}
16023-
```
1602416013

16014+
##### 锁粗化
1602516015

16016+
对相同对象多次加锁,导致线程发生多次重入,频繁的加锁操作就会导致性能损耗,可以使用锁粗化方式优化
1602616017

16027-
***
16018+
如果虚拟机探测到一串零碎的操作都对同一个对象加锁,将会把加锁的范围扩展(粗化)到整个操作序列的外部
1602816019

16020+
* 一些看起来没有加锁的代码,其实隐式的加了很多锁:
1602916021

16022+
```java
16023+
public static String concatString(String s1, String s2, String s3) {
16024+
return s1 + s2 + s3;
16025+
}
16026+
```
1603016027

16031-
##### 锁粗化
16028+
* String 是一个不可变的类,编译器会对 String 的拼接自动优化。在 JDK 1.5 之前,转化为StringBuffer对象的连续 append() 操作,每个append() 方法中都有一个同步块
1603216029

16033-
对相同对象多次加锁,导致线程发生多次重入,频繁的加锁操作就会导致性能损耗,可以使用锁粗化方式优化
16030+
```java
16031+
public static String concatString(String s1, String s2, String s3) {
16032+
StringBuffer sb = new StringBuffer();
16033+
sb.append(s1);
16034+
sb.append(s2);
16035+
sb.append(s3);
16036+
return sb.toString();
16037+
}
16038+
```
1603416039

16035-
上一节的示例代码中连续的 append() 方法就属于这类情况。如果虚拟机探测到由这样的一串零碎的操作都对同一个对象加锁,将会把加锁的范围扩展(粗化)到整个操作序列的外部。对于上一节的示例代码就是扩展到第一个 append() 操作之前直至最后一个 append() 操作之后,只需要加锁一次就可以
16040+
扩展到第一个 append() 操作之前直至最后一个 append() 操作之后,只需要加锁一次就可以
1603616041

1603716042

1603816043

@@ -16171,9 +16176,7 @@ class HoldLockThread implements Runnable {
1617116176

1617216177
定位死锁的方法:
1617316178

16174-
* 检测死锁可以使用 jconsole工具,或者使用 jps 定位进程 id,再用 `jstack id`定位死锁
16175-
16176-
找到死锁的线程去查看源码,解决优化
16179+
* 使用 jps 定位进程 id,再用 `jstack id`定位死锁,找到死锁的线程去查看源码,解决优化
1617716180

1617816181
```sh
1617916182
"Thread-1" #12 prio=5 os_prio=0 tid=0x000000001eb69000 nid=0xd40 waiting formonitor entry [0x000000001f54f000]
@@ -16206,11 +16209,13 @@ class HoldLockThread implements Runnable {
1620616209
- locked <0x000000076b5bf1c0> (a java.lang.Object)
1620716210
at thread.TestDeadLock$$Lambda$1/495053715
1620816211
```
16209-
16212+
1621016213
* linux 下可以通过 top 先定位到CPU 占用高的 Java 进程,再利用 `top -Hp 进程id` 来定位是哪个线程,最后再用 jstack 排查
1621116214

1621216215
* 避免死锁:避免死锁要注意加锁顺序
1621316216

16217+
* 也可以使用 jconsole工具,在jdk\bin目录下
16218+
1621416219

1621516220

1621616221
***
@@ -18637,6 +18642,14 @@ private static int ctlOf(int rs, int wc) { return rs | wc; }
1863718642

1863818643
##### Executor
1863918644

18645+
存放线程的容器:
18646+
18647+
```java
18648+
private final HashSet<Worker> workers = new HashSet<Worker>();
18649+
```
18650+
18651+
构造方法:
18652+
1864018653
```java
1864118654
public ThreadPoolExecutor( int corePoolSize,
1864218655
int maximumPoolSize,
@@ -18681,7 +18694,7 @@ public ThreadPoolExecutor( int corePoolSize,
1868118694

1868218695
![](https://gitee.com/seazean/images/raw/master/Java/JUC-线程池工作原理.png)
1868318696

18684-
1. 在创建了线程池后,等待提交过来的任务请求
18697+
1. 创建线程池,这时没有创建线程(**懒惰**),等待提交过来的任务请求,调用execute方法才会创建线程
1868518698

1868618699
2. 当调用execute()方法添加一个请求任务时,线程池会做如下判断:
1868718700
* 如果正在运行的线程数量小于corePoolSize,那么马上创建线程运行这个任务
@@ -18760,7 +18773,7 @@ Executors提供了四种线程池的创建:newCachedThreadPool、newFixedThrea
1876018773

1876118774

1876218775

18763-
##### 开发要求
18776+
#### 开发要求
1876418777

1876518778
阿里巴巴Java开发手册要求
1876618779

@@ -18778,6 +18791,25 @@ Executors提供了四种线程池的创建:newCachedThreadPool、newFixedThrea
1877818791
- CacheThreadPool和ScheduledThreadPool:
1877918792
- 允许的创建线程数量为 Integer.MAX_VALUE,可能会创建大量的线程,从而导致 OOM
1878018793

18794+
创建多大容量的线程池合适?
18795+
18796+
* 一般来说池中**总线程数是核心池线程数量两倍**,确保当核心池有线程停止时,核心池外有线程进入核心池
18797+
18798+
* 过小会导致程序不能充分地利用系统资源、容易导致饥饿
18799+
18800+
* 过大会导致更多的线程上下文切换,占用更多内存
18801+
上下文切换:当前任务在执行完 CPU 时间片切换到另一个任务之前会先保存自己的状态,以便下次再切换回这个任务时,可以再加载这个任务的状态,任务从保存到再加载的过程就是一次上下文切换
18802+
18803+
核心线程数常用公式:
18804+
18805+
- **CPU 密集型任务(N+1):** 这种任务消耗的是 CPU 资源,可以将核心线程数设置为 N (CPU 核心数)+1,比 CPU 核心数多出来的一个线程是为了防止线程发生缺页中断,或者其它原因导致的任务暂停而带来的影响。一旦任务暂停,CPU 就会处于空闲状态,而在这种情况下多出来的一个线程就可以充分利用 CPU 的空闲时间
18806+
18807+
CPU 密集型简单理解就是利用 CPU 计算能力的任务比如你在内存中对大量数据进行分析
18808+
18809+
- **I/O 密集型任务:** 这种系统 CPU 处于阻塞状态,用大部分的时间来处理 I/O 交互,而线程在处理 I/O 的时间段内不会占用 CPU 来处理,这时就可以将 CPU 交出给其它线程使用,因此在 I/O 密集型任务的应用中,我们可以多配置一些线程,具体的计算方法是 2N 或 CPU核数/ (1-阻塞系数),阻塞系数在0.8~0.9之间
18810+
18811+
IO 密集型就是涉及到网络读取,文件读取此类任务 ,特点是 CPU 计算耗费时间相比于等待 IO 操作完成的时间来说很少,大部分时间都花在了等待 IO 操作完成上
18812+
1878118813

1878218814

1878318815

@@ -18861,33 +18893,6 @@ System.out.println(future.get());
1886118893

1886218894

1886318895

18864-
***
18865-
18866-
18867-
18868-
#### 数量设置
18869-
18870-
创建多大容量的线程池合适?
18871-
18872-
* 一般来说池中**总线程数是核心池线程数量两倍**,确保当核心池有线程停止时,核心池外有线程进入核心池
18873-
18874-
* 过小会导致程序不能充分地利用系统资源、容易导致饥饿
18875-
18876-
* 过大会导致更多的线程上下文切换,占用更多内存
18877-
上下文切换:当前任务在执行完 CPU 时间片切换到另一个任务之前会先保存自己的状态,以便下次再切换回这个任务时,可以再加载这个任务的状态,任务从保存到再加载的过程就是一次上下文切换
18878-
18879-
核心线程数常用公式:
18880-
18881-
- **CPU 密集型任务(N+1):** 这种任务消耗的是 CPU 资源,可以将核心线程数设置为 N (CPU 核心数)+1,比 CPU 核心数多出来的一个线程是为了防止线程发生缺页中断,或者其它原因导致的任务暂停而带来的影响。一旦任务暂停,CPU 就会处于空闲状态,而在这种情况下多出来的一个线程就可以充分利用 CPU 的空闲时间
18882-
18883-
CPU 密集型简单理解就是利用 CPU 计算能力的任务比如你在内存中对大量数据进行分析
18884-
18885-
- **I/O 密集型任务:** 这种系统 CPU 处于阻塞状态,用大部分的时间来处理 I/O 交互,而线程在处理 I/O 的时间段内不会占用 CPU 来处理,这时就可以将 CPU 交出给其它线程使用,因此在 I/O 密集型任务的应用中,我们可以多配置一些线程,具体的计算方法是 2N 或 CPU核数/ (1-阻塞系数),阻塞系数在0.8~0.9之间
18886-
18887-
IO 密集型就是涉及到网络读取,文件读取此类任务 ,特点是 CPU 计算耗费时间相比于等待 IO 操作完成的时间来说很少,大部分时间都花在了等待 IO 操作完成上
18888-
18889-
18890-
1889118896

1889218897

1889318898
***
@@ -18936,6 +18941,13 @@ private static void method1() {
1893618941

1893718942
构造方法:`Executors.newScheduledThreadPool(int corePoolSize)`
1893818943

18944+
```java
18945+
public ScheduledThreadPoolExecutor(int corePoolSize) {
18946+
super(corePoolSize, Integer.MAX_VALUE, 0, NANOSECONDS,
18947+
new DelayedWorkQueue());
18948+
}
18949+
```
18950+
1893918951
常用API:
1894018952

1894118953
* `ScheduledFuture<?> schedule(Runnable/Callable<V>, long delay, TimeUnit u)`:延迟执行任务
@@ -19663,4 +19675,4 @@ public class BufferedInputStrem extends InputStream {
1966319675

1966419676

1966519677

19666-
19678+

0 commit comments

Comments
 (0)