mirror of
https://github.com/Snailclimb/JavaGuide
synced 2025-06-16 18:10:13 +08:00
[docs update]markdown格式规范
This commit is contained in:
parent
eb51143b87
commit
6469d6c097
1
.gitignore
vendored
1
.gitignore
vendored
@ -15,3 +15,4 @@ packages/*/lib/
|
||||
traversal-folder-replace-string.py
|
||||
format-markdown.py
|
||||
package-lock.json
|
||||
lintmd-config.json
|
||||
|
@ -90,7 +90,7 @@ MySQL 字段类型比较多,我这里会挑选一些日常开发使用很频
|
||||
|
||||
MySQL 中的整数类型可以使用可选的 UNSIGNED 属性来表示不允许负值的无符号整数。使用 UNSIGNED 属性可以将正整数的上限提高一倍,因为它不需要存储负数值。
|
||||
|
||||
例如, TINYINT UNSIGNED 类型的取值范围是 0 ~ 255,而普通的 TINYINT 类型的值范围是 -128 ~ 127。INT UNSIGNED 类型的取值范围是 0 ~ 4,294,967,295,而普通的 TINYINT 类型的值范围是 2,147,483,648 ~ 2,147,483,647。
|
||||
例如, TINYINT UNSIGNED 类型的取值范围是 0 ~ 255,而普通的 TINYINT 类型的值范围是 -128 ~ 127。INT UNSIGNED 类型的取值范围是 0 ~ 4,294,967,295,而普通的 INT 类型的值范围是 2,147,483,648 ~ 2,147,483,647。
|
||||
|
||||
对于从 0 开始递增的 ID 列,使用 UNSIGNED 属性可以非常适合,因为不允许负值并且可以拥有更大的上限范围,提供了更多的 ID 值可用。
|
||||
|
||||
|
@ -27,7 +27,7 @@ head:
|
||||
7. 高效性(通过 Just In Time 编译器等技术的优化,Java 语言的运行效率还是非常不错的);
|
||||
8. 支持网络编程并且很方便;
|
||||
9. 编译与解释并存;
|
||||
10. ......
|
||||
10. ……
|
||||
|
||||
> **🐛 修正(参见:[issue#544](https://github.com/Snailclimb/JavaGuide/issues/544))**:C++11 开始(2011 年的时候),C++就引入了多线程库,在 windows、linux、macos 都可以使用`std::thread`和`std::async`来创建线程。参考链接:http://www.cplusplus.com/reference/thread/thread/?kw=thread
|
||||
|
||||
@ -185,7 +185,7 @@ JDK 9 引入了一种新的编译模式 **AOT(Ahead of Time Compilation)** 。
|
||||
- Java 的类是单继承的,C++ 支持多重继承;虽然 Java 的类不可以多继承,但是接口可以多继承。
|
||||
- Java 有自动内存管理垃圾回收机制(GC),不需要程序员手动释放无用内存。
|
||||
- C ++同时支持方法重载和操作符重载,但是 Java 只支持方法重载(操作符重载增加了复杂性,这与 Java 最初的设计思想不符)。
|
||||
- ......
|
||||
- ……
|
||||
|
||||
## 基本语法
|
||||
|
||||
@ -320,7 +320,7 @@ System.out.println("左移 10 位后的数据对应的二进制字符 " + Intege
|
||||
|
||||
输出:
|
||||
|
||||
```
|
||||
```plain
|
||||
初始数据:-1
|
||||
初始数据对应的二进制字符串:11111111111111111111111111111111
|
||||
左移 10 位后的数据 -1024
|
||||
@ -384,7 +384,7 @@ System.out.println("左移 10 位后的数据对应的二进制字符 " + Intege
|
||||
|
||||
运行结果:
|
||||
|
||||
```
|
||||
```plain
|
||||
0
|
||||
xixi
|
||||
1
|
||||
@ -681,16 +681,13 @@ System.out.println(l + 1 == Long.MIN_VALUE); // true
|
||||
- **生存时间**:从变量在内存中的生存时间上看,成员变量是对象的一部分,它随着对象的创建而存在,而局部变量随着方法的调用而自动生成,随着方法的调用结束而消亡。
|
||||
- **默认值**:从变量是否有默认值来看,成员变量如果没有被赋初始值,则会自动以类型的默认值而赋值(一种情况例外:被 `final` 修饰的成员变量也必须显式地赋值),而局部变量则不会自动赋值。
|
||||
|
||||
> 问:为什么成员变量有默认值?
|
||||
>
|
||||
> 答:
|
||||
>
|
||||
> 1. 先不考虑变量类型,如果没有默认值会怎样?变量存储的是内存地址对应的任意随机值,程序读取该值运行会出现意外。
|
||||
>
|
||||
> 2. 默认值有两种设置方式:手动和自动,根据第一点,没有手动赋值一定要自动赋值。成员变量在运行时可借助反射等方法手动赋值,而局部变量不行。
|
||||
>
|
||||
> 3. 对于编译器(javac)来说,局部变量没赋值很好判断,可以直接报错。而成员变量可能是运行时赋值,无法判断,误报“没默认值”又会影响用户体验,所以采用自动赋默认值。
|
||||
>
|
||||
**为什么成员变量有默认值?**
|
||||
|
||||
1. 先不考虑变量类型,如果没有默认值会怎样?变量存储的是内存地址对应的任意随机值,程序读取该值运行会出现意外。
|
||||
|
||||
2. 默认值有两种设置方式:手动和自动,根据第一点,没有手动赋值一定要自动赋值。成员变量在运行时可借助反射等方法手动赋值,而局部变量不行。
|
||||
|
||||
3. 对于编译器(javac)来说,局部变量没赋值很好判断,可以直接报错。而成员变量可能是运行时赋值,无法判断,误报“没默认值”又会影响用户体验,所以采用自动赋默认值。
|
||||
|
||||
成员变量与局部变量代码示例:
|
||||
|
||||
@ -776,7 +773,7 @@ public class StringExample {
|
||||
|
||||
输出:
|
||||
|
||||
```
|
||||
```plain
|
||||
字符型常量占用的字节数为:2
|
||||
字符串常量占用的字节数为:13
|
||||
```
|
||||
@ -1022,7 +1019,7 @@ public class VariableLengthArgument {
|
||||
|
||||
输出:
|
||||
|
||||
```
|
||||
```plain
|
||||
ab
|
||||
a
|
||||
b
|
||||
|
@ -115,7 +115,7 @@ System.out.println(str1.equals(str3));
|
||||
|
||||
输出结果:
|
||||
|
||||
```
|
||||
```plain
|
||||
false
|
||||
true
|
||||
true
|
||||
|
@ -49,7 +49,7 @@ head:
|
||||
- `ArithmeticException`(算术错误)
|
||||
- `SecurityException` (安全错误比如权限不够)
|
||||
- `UnsupportedOperationException`(不支持的操作错误比如重复创建同一用户)
|
||||
- ......
|
||||
- ……
|
||||
|
||||

|
||||
|
||||
@ -81,7 +81,7 @@ try {
|
||||
|
||||
输出:
|
||||
|
||||
```
|
||||
```plain
|
||||
Try to do something
|
||||
Catch Exception -> RuntimeException
|
||||
Finally
|
||||
@ -117,7 +117,7 @@ public static int f(int value) {
|
||||
|
||||
输出:
|
||||
|
||||
```
|
||||
```plain
|
||||
0
|
||||
```
|
||||
|
||||
@ -142,7 +142,7 @@ try {
|
||||
|
||||
输出:
|
||||
|
||||
```
|
||||
```plain
|
||||
Try to do something
|
||||
Catch Exception -> RuntimeException
|
||||
```
|
||||
@ -219,7 +219,7 @@ catch (IOException e) {
|
||||
- 抛出的异常信息一定要有意义。
|
||||
- 建议抛出更加具体的异常比如字符串转换为数字格式错误的时候应该抛出`NumberFormatException`而不是其父类`IllegalArgumentException`。
|
||||
- 使用日志打印异常之后就不要再抛出异常了(两者不要同时存在一段代码逻辑中)。
|
||||
- ......
|
||||
- ……
|
||||
|
||||
## 泛型
|
||||
|
||||
@ -323,7 +323,7 @@ printArray( stringArray );
|
||||
- 自定义接口通用返回结果 `CommonResult<T>` 通过参数 `T` 可根据具体的返回类型动态指定结果的数据类型
|
||||
- 定义 `Excel` 处理类 `ExcelUtil<T>` 用于动态指定 `Excel` 导出的数据类型
|
||||
- 构建集合工具类(参考 `Collections` 中的 `sort`, `binarySearch` 方法)。
|
||||
- ......
|
||||
- ……
|
||||
|
||||
## 反射
|
||||
|
||||
|
@ -150,7 +150,7 @@ public class StaticDemo {
|
||||
|
||||
静态代码块的格式是
|
||||
|
||||
```
|
||||
```plain
|
||||
static {
|
||||
语句体;
|
||||
}
|
||||
@ -282,19 +282,19 @@ public class Test {
|
||||
|
||||
上述代码输出:
|
||||
|
||||
```
|
||||
```plain
|
||||
静态代码块!--非静态代码块!--默认构造方法!--静态方法中的内容! --静态方法中的代码块!--
|
||||
```
|
||||
|
||||
当只执行 `Test.test();` 时输出:
|
||||
|
||||
```
|
||||
```plain
|
||||
静态代码块!--静态方法中的内容! --静态方法中的代码块!--
|
||||
```
|
||||
|
||||
当只执行 `Test test = new Test();` 时输出:
|
||||
|
||||
```
|
||||
```plain
|
||||
静态代码块!--非静态代码块!--默认构造方法!--
|
||||
```
|
||||
|
||||
|
@ -248,7 +248,7 @@ smsService.send("java");
|
||||
|
||||
运行上述代码之后,控制台打印出:
|
||||
|
||||
```
|
||||
```plain
|
||||
before method send
|
||||
send message:java
|
||||
after method send
|
||||
|
@ -170,7 +170,7 @@ public class Main {
|
||||
|
||||
输出内容:
|
||||
|
||||
```
|
||||
```plain
|
||||
publicMethod
|
||||
privateMethod
|
||||
I love JavaGuide
|
||||
|
@ -58,7 +58,7 @@ SLF4J (Simple Logging Facade for Java)是 Java 的一个日志门面(接
|
||||
|
||||
新建一个 Java 项目 `service-provider-interface` 目录结构如下:(注意直接新建 Java 项目就好了,不用新建 Maven 项目,Maven 项目会涉及到一些编译配置,如果有私服的话,直接 deploy 会比较方便,但是没有的话,在过程中可能会遇到一些奇怪的问题。)
|
||||
|
||||
```
|
||||
```plain
|
||||
│ service-provider-interface.iml
|
||||
│
|
||||
├─.idea
|
||||
@ -171,7 +171,7 @@ public class Main {
|
||||
|
||||
新建项目 `service-provider` 目录结构如下:
|
||||
|
||||
```
|
||||
```plain
|
||||
│ service-provider.iml
|
||||
│
|
||||
├─.idea
|
||||
@ -290,7 +290,7 @@ public class TestJavaSPI {
|
||||
|
||||
`ServiceLoader` 是 JDK 提供的一个工具类, 位于`package java.util;`包下。
|
||||
|
||||
```
|
||||
```plain
|
||||
A facility to load implementations of a service.
|
||||
```
|
||||
|
||||
|
@ -777,7 +777,7 @@ public static void main(String[] args) {
|
||||
|
||||
输出结果:
|
||||
|
||||
```
|
||||
```plain
|
||||
a == b is false
|
||||
c == d is true
|
||||
```
|
||||
|
@ -152,7 +152,7 @@ private void memoryTest() {
|
||||
|
||||
先看结果输出:
|
||||
|
||||
```
|
||||
```plain
|
||||
addr: 2433733895744
|
||||
addr3: 2433733894944
|
||||
16843009
|
||||
@ -275,7 +275,7 @@ public static void main(String[] args){
|
||||
|
||||
运行结果:
|
||||
|
||||
```
|
||||
```plain
|
||||
subThread change flag to:false
|
||||
detected flag changed
|
||||
main thread end
|
||||
@ -341,7 +341,7 @@ public class Main {
|
||||
|
||||
输出结果:
|
||||
|
||||
```
|
||||
```plain
|
||||
value before putInt: 0
|
||||
value after putInt: 42
|
||||
value after putInt: 42
|
||||
@ -510,7 +510,7 @@ private void increment(int x){
|
||||
|
||||
运行代码会依次输出:
|
||||
|
||||
```
|
||||
```plain
|
||||
1 2 3 4 5 6 7 8 9
|
||||
```
|
||||
|
||||
@ -600,7 +600,7 @@ public static void main(String[] args) {
|
||||
|
||||
程序输出为:
|
||||
|
||||
```
|
||||
```plain
|
||||
park main mainThread
|
||||
subThread try to unpark mainThread
|
||||
unpark mainThread success
|
||||
@ -653,7 +653,7 @@ private void staticTest() throws Exception {
|
||||
|
||||
运行结果:
|
||||
|
||||
```
|
||||
```plain
|
||||
false
|
||||
Hydra
|
||||
```
|
||||
@ -662,7 +662,7 @@ Hydra
|
||||
|
||||
在上面的代码中首先创建一个`User`对象,这是因为如果一个类没有被初始化,那么它的静态属性也不会被初始化,最后获取的字段属性将是`null`。所以在获取静态属性前,需要调用`shouldBeInitialized`方法,判断在获取前是否需要初始化这个类。如果删除创建 User 对象的语句,运行结果会变为:
|
||||
|
||||
```
|
||||
```plain
|
||||
true
|
||||
null
|
||||
```
|
||||
|
@ -64,7 +64,7 @@ public static void swap(int a, int b) {
|
||||
|
||||
输出:
|
||||
|
||||
```
|
||||
```plain
|
||||
a = 20
|
||||
b = 10
|
||||
num1 = 10
|
||||
@ -99,7 +99,7 @@ num2 = 20
|
||||
|
||||
输出:
|
||||
|
||||
```
|
||||
```plain
|
||||
1
|
||||
0
|
||||
```
|
||||
@ -143,7 +143,7 @@ public static void swap(Person person1, Person person2) {
|
||||
|
||||
输出:
|
||||
|
||||
```
|
||||
```plain
|
||||
person1:小李
|
||||
person2:小张
|
||||
xiaoZhang:小张
|
||||
@ -184,7 +184,7 @@ int main()
|
||||
|
||||
输出结果:
|
||||
|
||||
```
|
||||
```plain
|
||||
invoke before: 10
|
||||
incr before: 10
|
||||
incr after: 11
|
||||
|
@ -48,7 +48,7 @@ System.out.println(listOfStrings);
|
||||
|
||||
输出:
|
||||
|
||||
```
|
||||
```plain
|
||||
[null, java]
|
||||
```
|
||||
|
||||
@ -823,7 +823,7 @@ public class ArraycopyTest {
|
||||
|
||||
结果:
|
||||
|
||||
```
|
||||
```plain
|
||||
0 1 99 2 3 0 0 0 0 0
|
||||
```
|
||||
|
||||
@ -872,7 +872,7 @@ public class ArrayscopyOfTest {
|
||||
|
||||
结果:
|
||||
|
||||
```
|
||||
```plain
|
||||
10
|
||||
```
|
||||
|
||||
@ -933,7 +933,7 @@ public class EnsureCapacityTest {
|
||||
|
||||
运行结果:
|
||||
|
||||
```
|
||||
```plain
|
||||
使用ensureCapacity方法前:2158
|
||||
```
|
||||
|
||||
@ -955,7 +955,7 @@ public class EnsureCapacityTest {
|
||||
|
||||
运行结果:
|
||||
|
||||
```
|
||||
```plain
|
||||
使用ensureCapacity方法后:1773
|
||||
```
|
||||
|
||||
|
@ -36,7 +36,7 @@ JDK1.5 引入了 `Java.util.concurrent`(JUC)包,其中提供了很多线
|
||||
1. 内存占用:每次写操作都需要复制一份原始数据,会占用额外的内存空间,在数据量比较大的情况下,可能会导致内存资源不足。
|
||||
2. 写操作开销:每一次写操作都需要复制一份原始数据,然后再进行修改和替换,所以写操作的开销相对较大,在写入比较频繁的场景下,性能可能会受到影响。
|
||||
3. 数据一致性问题:修改操作不会立即反映到最终结果中,还需要等待复制完成,这可能会导致一定的数据一致性问题。
|
||||
4. ......
|
||||
4. ……
|
||||
|
||||
## CopyOnWriteArrayList 源码分析
|
||||
|
||||
@ -305,7 +305,7 @@ System.out.println("列表清空后为:" + list);
|
||||
|
||||
输出:
|
||||
|
||||
```
|
||||
```plain
|
||||
列表更新后为:[Java, Golang]
|
||||
列表插入元素后为:[PHP, Java, Golang]
|
||||
列表大小为:3
|
||||
|
@ -141,7 +141,7 @@ System.out.println(list); /* [1, 3, 5, 7, 9] */
|
||||
|
||||
- 使用普通的 for 循环
|
||||
- 使用 fail-safe 的集合类。`java.util`包下面的所有的集合类都是 fail-fast 的,而`java.util.concurrent`包下面的所有的类都是 fail-safe 的。
|
||||
- ......
|
||||
- ……
|
||||
|
||||
## 集合去重
|
||||
|
||||
|
@ -147,7 +147,7 @@ System.out.println(listOfStrings);
|
||||
|
||||
输出:
|
||||
|
||||
```
|
||||
```plain
|
||||
[null, java]
|
||||
```
|
||||
|
||||
@ -299,7 +299,7 @@ System.out.println(arrayList);
|
||||
|
||||
Output:
|
||||
|
||||
```
|
||||
```plain
|
||||
原始数组:
|
||||
[-1, 3, 3, -5, 7, 4, -9, -7]
|
||||
Collections.reverse(arrayList):
|
||||
@ -377,7 +377,7 @@ public class Person implements Comparable<Person> {
|
||||
|
||||
Output:
|
||||
|
||||
```
|
||||
```plain
|
||||
5-小红
|
||||
10-王五
|
||||
20-李四
|
||||
@ -476,7 +476,7 @@ Java 中常用的阻塞队列实现类有以下几种:
|
||||
3. `PriorityBlockingQueue`:支持优先级排序的无界阻塞队列。元素必须实现`Comparable`接口或者在构造函数中传入`Comparator`对象,并且不能插入 null 元素。
|
||||
4. `SynchronousQueue`:同步队列,是一种不存储元素的阻塞队列。每个插入操作都必须等待对应的删除操作,反之删除操作也必须等待插入操作。因此,`SynchronousQueue`通常用于线程之间的直接传递数据。
|
||||
5. `DelayQueue`:延迟队列,其中的元素只有到了其指定的延迟时间,才能够从队列中出队。
|
||||
6. ......
|
||||
6. ……
|
||||
|
||||
日常开发中,这些队列使用的其实都不多,了解即可。
|
||||
|
||||
|
@ -120,7 +120,7 @@ public class Person {
|
||||
|
||||
输出:
|
||||
|
||||
```
|
||||
```plain
|
||||
person1
|
||||
person4
|
||||
person2
|
||||
@ -356,7 +356,7 @@ final V putVal(int hash, K key, V value, boolean onlyIfAbsent,
|
||||
|
||||
当遍历不存在阻塞时, parallelStream 的性能是最低的:
|
||||
|
||||
```
|
||||
```plain
|
||||
Benchmark Mode Cnt Score Error Units
|
||||
Test.entrySet avgt 5 288.651 ± 10.536 ns/op
|
||||
Test.keySet avgt 5 584.594 ± 21.431 ns/op
|
||||
@ -366,7 +366,7 @@ Test.parallelStream avgt 5 6919.163 ± 1116.139 ns/op
|
||||
|
||||
加入阻塞代码`Thread.sleep(10)`后, parallelStream 的性能才是最高的:
|
||||
|
||||
```
|
||||
```plain
|
||||
Benchmark Mode Cnt Score Error Units
|
||||
Test.entrySet avgt 5 1554828440.000 ± 23657748.653 ns/op
|
||||
Test.keySet avgt 5 1550612500.000 ± 6474562.858 ns/op
|
||||
|
@ -505,7 +505,7 @@ System.out.println("清空后的链表:" + list);
|
||||
|
||||
输出:
|
||||
|
||||
```
|
||||
```plain
|
||||
索引为 2 的元素:banana
|
||||
链表内容:[apple, orange, banana, grape]
|
||||
链表内容:[orange, banana, grape]
|
||||
|
@ -688,7 +688,7 @@ public class CyclicBarrierExample1 {
|
||||
|
||||
运行结果,如下:
|
||||
|
||||
```
|
||||
```plain
|
||||
threadnum:0is ready
|
||||
threadnum:1is ready
|
||||
threadnum:2is ready
|
||||
@ -760,7 +760,7 @@ public class CyclicBarrierExample2 {
|
||||
|
||||
运行结果,如下:
|
||||
|
||||
```
|
||||
```plain
|
||||
threadnum:0is ready
|
||||
threadnum:1is ready
|
||||
threadnum:2is ready
|
||||
|
@ -257,7 +257,7 @@ class Person {
|
||||
|
||||
上述代码首先创建了一个 `Person` 对象,然后把 `Person` 对象设置进 `AtomicReference` 对象中,然后调用 `compareAndSet` 方法,该方法就是通过 CAS 操作设置 ar。如果 ar 的值为 `person` 的话,则将其设置为 `updatePerson`。实现原理与 `AtomicInteger` 类中的 `compareAndSet` 方法相同。运行上面的代码后的输出结果如下:
|
||||
|
||||
```
|
||||
```plain
|
||||
Daisy
|
||||
20
|
||||
```
|
||||
@ -312,7 +312,7 @@ public class AtomicStampedReferenceDemo {
|
||||
|
||||
输出结果如下:
|
||||
|
||||
```
|
||||
```plain
|
||||
currentValue=0, currentStamp=0
|
||||
currentValue=666, currentStamp=999, casResult=true
|
||||
currentValue=666, currentStamp=999
|
||||
@ -371,7 +371,7 @@ public class AtomicMarkableReferenceDemo {
|
||||
|
||||
输出结果如下:
|
||||
|
||||
```
|
||||
```plain
|
||||
currentValue=null, currentMark=false
|
||||
currentValue=true, currentMark=true, casResult=true
|
||||
currentValue=true, currentMark=true
|
||||
@ -438,7 +438,7 @@ class User {
|
||||
|
||||
输出结果:
|
||||
|
||||
```
|
||||
```plain
|
||||
22
|
||||
23
|
||||
```
|
||||
|
@ -533,7 +533,7 @@ try {
|
||||
|
||||
输出:
|
||||
|
||||
```
|
||||
```plain
|
||||
任务1开始执行,当前时间:1695088058520
|
||||
任务2开始执行,当前时间:1695088058521
|
||||
任务1执行完毕,当前时间:1695088059023
|
||||
@ -614,7 +614,7 @@ System.out.println("all futures done...");
|
||||
|
||||
输出:
|
||||
|
||||
```java
|
||||
```plain
|
||||
future1 done...
|
||||
future2 done...
|
||||
all futures done...
|
||||
@ -629,14 +629,14 @@ System.out.println(f.get());
|
||||
|
||||
输出结果可能是:
|
||||
|
||||
```java
|
||||
```plain
|
||||
future2 done...
|
||||
efg
|
||||
```
|
||||
|
||||
也可能是:
|
||||
|
||||
```
|
||||
```plain
|
||||
future1 done...
|
||||
abc
|
||||
```
|
||||
@ -696,7 +696,7 @@ CompletableFuture.runAsync(() -> {
|
||||
- 使用 `exceptionally` 方法可以处理异常并重新抛出,以便异常能够传播到后续阶段,而不是让异常被忽略或终止。
|
||||
- 使用 `handle` 方法可以处理正常的返回结果和异常,并返回一个新的结果,而不是让异常影响正常的业务逻辑。
|
||||
- 使用 `CompletableFuture.allOf` 方法可以组合多个 `CompletableFuture`,并统一处理所有任务的异常,而不是让异常处理过于冗长或重复。
|
||||
- ......
|
||||
- ……
|
||||
|
||||
### 合理组合多个异步任务
|
||||
|
||||
|
@ -49,7 +49,7 @@ public class MultiThread {
|
||||
|
||||
上述程序输出如下(输出内容可能不同,不用太纠结下面每个线程的作用,只用知道 main 线程执行 main 方法即可):
|
||||
|
||||
```
|
||||
```plain
|
||||
[5] Attach Listener //添加事件
|
||||
[4] Signal Dispatcher // 分发处理给 JVM 信号的线程
|
||||
[3] Finalizer //调用对象 finalize 方法的线程
|
||||
@ -240,7 +240,7 @@ public class DeadLockDemo {
|
||||
|
||||
Output
|
||||
|
||||
```
|
||||
```plain
|
||||
Thread[线程 1,5,main]get resource1
|
||||
Thread[线程 2,5,main]get resource2
|
||||
Thread[线程 1,5,main]waiting get resource2
|
||||
@ -268,7 +268,7 @@ Thread[线程 2,5,main]waiting get resource1
|
||||
|
||||
避免死锁就是在资源分配时,借助于算法(比如银行家算法)对资源分配进行计算评估,使其进入安全状态。
|
||||
|
||||
> **安全状态** 指的是系统能够按照某种线程推进顺序(P1、P2、P3.....Pn)来为每个线程分配所需资源,直到满足每个线程对资源的最大需求,使每个线程都可顺利完成。称 `<P1、P2、P3.....Pn>` 序列为安全序列。
|
||||
> **安全状态** 指的是系统能够按照某种线程推进顺序(P1、P2、P3……Pn)来为每个线程分配所需资源,直到满足每个线程对资源的最大需求,使每个线程都可顺利完成。称 `<P1、P2、P3.....Pn>` 序列为安全序列。
|
||||
|
||||
我们对线程 2 的代码修改成下面这样就不会产生死锁了。
|
||||
|
||||
@ -291,7 +291,7 @@ new Thread(() -> {
|
||||
|
||||
输出:
|
||||
|
||||
```
|
||||
```plain
|
||||
Thread[线程 1,5,main]get resource1
|
||||
Thread[线程 1,5,main]waiting get resource2
|
||||
Thread[线程 1,5,main]get resource2
|
||||
|
@ -68,7 +68,7 @@ public class ThreadLocalExample implements Runnable{
|
||||
|
||||
输出结果 :
|
||||
|
||||
```
|
||||
```plain
|
||||
Thread Name= 0 default Formatter = yyyyMMdd HHmm
|
||||
Thread Name= 0 formatter = yy-M-d ah:mm
|
||||
Thread Name= 1 default Formatter = yyyyMMdd HHmm
|
||||
|
@ -125,7 +125,7 @@ public final class NamingThreadFactory implements ThreadFactory {
|
||||
|
||||
说到如何给线程池配置参数,美团的骚操作至今让我难忘(后面会提到)!
|
||||
|
||||
我们先来看一下各种书籍和博客上一般推荐的配置线程池参数的方式,可以作为参考!
|
||||
我们先来看一下各种书籍和博客上一般推荐的配置线程池参数的方式,可以作为参考。
|
||||
|
||||
### 常规操作
|
||||
|
||||
@ -153,19 +153,19 @@ public final class NamingThreadFactory implements ThreadFactory {
|
||||
|
||||
CPU 密集型简单理解就是利用 CPU 计算能力的任务比如你在内存中对大量数据进行排序。但凡涉及到网络读取,文件读取这类都是 IO 密集型,这类任务的特点是 CPU 计算耗费时间相比于等待 IO 操作完成的时间来说很少,大部分时间都花在了等待 IO 操作完成上。
|
||||
|
||||
> 🌈 拓展一下(参见:[issue#1737](https://github.com/Snailclimb/JavaGuide/issues/1737)):
|
||||
>
|
||||
> 线程数更严谨的计算的方法应该是:`最佳线程数 = N(CPU 核心数)∗(1+WT(线程等待时间)/ST(线程计算时间))`,其中 `WT(线程等待时间)=线程运行总时间 - ST(线程计算时间)`。
|
||||
>
|
||||
> 线程等待时间所占比例越高,需要越多线程。线程计算时间所占比例越高,需要越少线程。
|
||||
>
|
||||
> 我们可以通过 JDK 自带的工具 VisualVM 来查看 `WT/ST` 比例。
|
||||
>
|
||||
> CPU 密集型任务的 `WT/ST` 接近或者等于 0,因此, 线程数可以设置为 N(CPU 核心数)∗(1+0)= N,和我们上面说的 N(CPU 核心数)+1 差不多。
|
||||
>
|
||||
> IO 密集型任务下,几乎全是线程等待时间,从理论上来说,你就可以将线程数设置为 2N(按道理来说,WT/ST 的结果应该比较大,这里选择 2N 的原因应该是为了避免创建过多线程吧)。
|
||||
🌈 拓展一下(参见:[issue#1737](https://github.com/Snailclimb/JavaGuide/issues/1737)):
|
||||
|
||||
**公示也只是参考,具体还是要根据项目实际线上运行情况来动态调整。我在后面介绍的美团的线程池参数动态配置这种方案就非常不错,很实用!**
|
||||
线程数更严谨的计算的方法应该是:`最佳线程数 = N(CPU 核心数)∗(1+WT(线程等待时间)/ST(线程计算时间))`,其中 `WT(线程等待时间)=线程运行总时间 - ST(线程计算时间)`。
|
||||
|
||||
线程等待时间所占比例越高,需要越多线程。线程计算时间所占比例越高,需要越少线程。
|
||||
|
||||
我们可以通过 JDK 自带的工具 VisualVM 来查看 `WT/ST` 比例。
|
||||
|
||||
CPU 密集型任务的 `WT/ST` 接近或者等于 0,因此, 线程数可以设置为 N(CPU 核心数)∗(1+0)= N,和我们上面说的 N(CPU 核心数)+1 差不多。
|
||||
|
||||
IO 密集型任务下,几乎全是线程等待时间,从理论上来说,你就可以将线程数设置为 2N(按道理来说,WT/ST 的结果应该比较大,这里选择 2N 的原因应该是为了避免创建过多线程吧)。
|
||||
|
||||
**注意**:上面提到的公示也只是参考,实际项目不太可能直接按照公式来设置线程池参数,毕竟不同的业务场景对应的需求不同,具体还是要根据项目实际线上运行情况来动态调整。接下来介绍的美团的线程池参数动态配置这种方案就非常不错,很实用!
|
||||
|
||||
### 美团的骚操作
|
||||
|
||||
|
@ -315,7 +315,7 @@ public class ThreadPoolExecutorDemo {
|
||||
|
||||
**输出结构**:
|
||||
|
||||
```
|
||||
```plain
|
||||
pool-1-thread-3 Start. Time = Sun Apr 12 11:14:37 CST 2020
|
||||
pool-1-thread-5 Start. Time = Sun Apr 12 11:14:37 CST 2020
|
||||
pool-1-thread-2 Start. Time = Sun Apr 12 11:14:37 CST 2020
|
||||
@ -579,7 +579,7 @@ executorService.shutdown();
|
||||
|
||||
输出:
|
||||
|
||||
```
|
||||
```plain
|
||||
abc
|
||||
```
|
||||
|
||||
@ -604,7 +604,7 @@ executorService.shutdown();
|
||||
|
||||
输出:
|
||||
|
||||
```
|
||||
```plain
|
||||
Exception in thread "main" java.util.concurrent.TimeoutException
|
||||
at java.util.concurrent.FutureTask.get(FutureTask.java:205)
|
||||
```
|
||||
|
@ -126,7 +126,7 @@ Java 内存模型的抽象示意图如下:
|
||||
- 一个变量在同一个时刻只允许一条线程对其进行 lock 操作,但 lock 操作可以被同一条线程重复执行多次,多次执行 lock 后,只有执行相同次数的 unlock 操作,变量才会被解锁。
|
||||
- 如果对一个变量执行 lock 操作,将会清空工作内存中此变量的值,在执行引擎使用这个变量前,需要重新执行 load 或 assign 操作初始化变量的值。
|
||||
- 如果一个变量事先没有被 lock 操作锁定,则不允许对它执行 unlock 操作,也不允许去 unlock 一个被其他线程锁定住的变量。
|
||||
- ......
|
||||
- ……
|
||||
|
||||
### Java 内存区域和 JMM 有何区别?
|
||||
|
||||
|
@ -24,7 +24,7 @@ tag:
|
||||
- `ThreadLocalMap.set()`方法实现原理?
|
||||
- `ThreadLocalMap.get()`方法实现原理?
|
||||
- 项目中`ThreadLocal`使用情况?遇到的坑?
|
||||
- ......
|
||||
- ……
|
||||
|
||||
上述的一些问题你是否都已经掌握的很清楚了呢?本文将围绕这些问题使用图文方式来剖析`ThreadLocal`的**点点滴滴**。
|
||||
|
||||
|
@ -64,7 +64,7 @@ try (InputStream fis = new FileInputStream("input.txt")) {
|
||||
|
||||
输出:
|
||||
|
||||
```
|
||||
```plain
|
||||
Number of remaining bytes:11
|
||||
The actual number of bytes skipped:2
|
||||
The content read from file:JavaGuide
|
||||
@ -233,7 +233,7 @@ try (FileReader fileReader = new FileReader("input.txt");) {
|
||||
|
||||
输出:
|
||||
|
||||
```
|
||||
```plain
|
||||
The actual number of bytes skipped:3
|
||||
The content read from file:我是Guide。
|
||||
```
|
||||
@ -296,7 +296,7 @@ BufferedInputStream bufferedInputStream = new BufferedInputStream(new FileInputS
|
||||
|
||||
我使用 `write(int b)` 和 `read()` 方法,分别通过字节流和字节缓冲流复制一个 `524.9 mb` 的 PDF 文件耗时对比如下:
|
||||
|
||||
```
|
||||
```plain
|
||||
使用缓冲流复制PDF文件总耗时:15428 毫秒
|
||||
使用普通字节流复制PDF文件总耗时:2555062 毫秒
|
||||
```
|
||||
@ -347,7 +347,7 @@ void copy_pdf_to_another_pdf_stream() {
|
||||
|
||||
这次我们使用 `read(byte b[])` 和 `write(byte b[], int off, int len)` 方法,分别通过字节流和字节缓冲流复制一个 524.9 mb 的 PDF 文件耗时对比如下:
|
||||
|
||||
```
|
||||
```plain
|
||||
使用缓冲流复制PDF文件总耗时:695 毫秒
|
||||
使用普通字节流复制PDF文件总耗时:989 毫秒
|
||||
```
|
||||
@ -516,7 +516,7 @@ System.out.println("读取之前的偏移量:" + randomAccessFile.getFilePoint
|
||||
|
||||
输出:
|
||||
|
||||
```
|
||||
```plain
|
||||
读取之前的偏移量:0,当前读取到的字符A,读取之后的偏移量:1
|
||||
读取之前的偏移量:6,当前读取到的字符G,读取之后的偏移量:7
|
||||
读取之前的偏移量:0,当前读取到的字符A,读取之后的偏移量:1
|
||||
|
@ -93,7 +93,7 @@ public static ByteBuffer allocateDirect(int capacity);
|
||||
|
||||
- `flip` :将缓冲区从写模式切换到读模式,它会将 `limit` 的值设置为当前 `position` 的值,将 `position` 的值设置为 0。
|
||||
- `clear`: 清空缓冲区,将缓冲区从读模式切换到写模式,并将 `position` 的值设置为 0,将 `limit` 的值设置为 `capacity` 的值。
|
||||
- ......
|
||||
- ……
|
||||
|
||||
Buffer 中数据变化的过程:
|
||||
|
||||
@ -254,7 +254,7 @@ Selector 还提供了一系列和 `select()` 相关的方法:
|
||||
- `int select(long timeout)`:可以设置超时时长的 `select()` 操作。
|
||||
- `int selectNow()`:执行一个立即返回的 `select()` 操作,相对于无参数的 `select()` 方法而言,该方法不会阻塞线程。
|
||||
- `Selector wakeup()`:使一个还未返回的 `select()` 方法立刻返回。
|
||||
- ......
|
||||
- ……
|
||||
|
||||
使用 Selector 实现网络读写的简单示例:
|
||||
|
||||
|
@ -97,11 +97,11 @@ ClassFile {
|
||||
| CONSTANT_utf8_info | 1 | UTF-8 编码的字符串 |
|
||||
| CONSTANT_Integer_info | 3 | 整形字面量 |
|
||||
| CONSTANT_Float_info | 4 | 浮点型字面量 |
|
||||
| CONSTANT_Long_info | 5 | 长整型字面量 |
|
||||
| CONSTANT_Double_info | 6 | 双精度浮点型字面量 |
|
||||
| CONSTANT_Class_info | 7 | 类或接口的符号引用 |
|
||||
| CONSTANT_String_info | 8 | 字符串类型字面量 |
|
||||
| CONSTANT_FieldRef_info | 9 | 字段的符号引用 |
|
||||
| CONSTANT_Long_info | 5 | 长整型字面量 |
|
||||
| CONSTANT_Double_info | 6 | 双精度浮点型字面量 |
|
||||
| CONSTANT_Class_info | 7 | 类或接口的符号引用 |
|
||||
| CONSTANT_String_info | 8 | 字符串类型字面量 |
|
||||
| CONSTANT_FieldRef_info | 9 | 字段的符号引用 |
|
||||
| CONSTANT_MethodRef_info | 10 | 类中方法的符号引用 |
|
||||
| CONSTANT_InterfaceMethodRef_info | 11 | 接口中方法的符号引用 |
|
||||
| CONSTANT_NameAndType_info | 12 | 字段或方法的符号引用 |
|
||||
|
@ -73,7 +73,7 @@ tag:
|
||||
- `java.lang.IllegalAccessError`:当类试图访问或修改它没有权限访问的字段,或调用它没有权限访问的方法时,抛出该异常。
|
||||
- `java.lang.NoSuchFieldError`:当类试图访问或修改一个指定的对象字段,而该对象不再包含该字段时,抛出该异常。
|
||||
- `java.lang.NoSuchMethodError`:当类试图访问一个指定的方法,而该方法不存在时,抛出该异常。
|
||||
- ......
|
||||
- ……
|
||||
|
||||
### 准备
|
||||
|
||||
|
@ -145,7 +145,7 @@ public class PrintClassLoaderTree {
|
||||
|
||||
输出结果(JDK 8 ):
|
||||
|
||||
```
|
||||
```plain
|
||||
|--sun.misc.Launcher$AppClassLoader@18b4aac2
|
||||
|--sun.misc.Launcher$ExtClassLoader@53bd815b
|
||||
|--null
|
||||
|
@ -182,7 +182,7 @@ public class DeadLockDemo {
|
||||
|
||||
Output
|
||||
|
||||
```
|
||||
```plain
|
||||
Thread[线程 1,5,main]get resource1
|
||||
Thread[线程 2,5,main]get resource2
|
||||
Thread[线程 1,5,main]waiting get resource2
|
||||
@ -260,7 +260,7 @@ JConsole 是基于 JMX 的可视化监视、管理工具。可以很方便的监
|
||||
|
||||
在使用 JConsole 连接时,远程进程地址如下:
|
||||
|
||||
```
|
||||
```plain
|
||||
外网访问 ip 地址:60001
|
||||
```
|
||||
|
||||
@ -302,7 +302,7 @@ VisualVM 基于 NetBeans 平台开发,因此他一开始就具备了插件扩
|
||||
- **dump 以及分析堆转储快照(jmap、jhat)。**
|
||||
- **方法级的程序运行性能分析,找到被调用最多、运行时间最长的方法。**
|
||||
- **离线程序快照:收集程序的运行时配置、线程 dump、内存 dump 等信息建立一个快照,可以将快照发送开发者处进行 Bug 反馈。**
|
||||
- **其他 plugins 的无限的可能性......**
|
||||
- **其他 plugins 的无限的可能性……**
|
||||
|
||||
这里就不具体介绍 VisualVM 的使用,如果想了解的话可以看:
|
||||
|
||||
|
@ -73,7 +73,7 @@ GC 调优策略中很重要的一条经验总结是这样说的:
|
||||
|
||||
比如下面的参数就是设置老年代与新生代内存的比值为 1。也就是说老年代和新生代所占比值为 1:1,新生代占整个堆栈的 1/2。
|
||||
|
||||
```
|
||||
```plain
|
||||
-XX:NewRatio=1
|
||||
```
|
||||
|
||||
|
@ -151,7 +151,7 @@ Java 堆是垃圾收集器管理的主要区域,因此也被称作 **GC 堆(
|
||||
|
||||
1. **`java.lang.OutOfMemoryError: GC Overhead Limit Exceeded`**:当 JVM 花太多时间执行垃圾回收并且只能回收很少的堆空间时,就会发生此错误。
|
||||
2. **`java.lang.OutOfMemoryError: Java heap space`** :假如在创建新的对象时, 堆内存中的空间不足以存放新创建的对象, 就会引发此错误。(和配置的最大堆内存有关,且受制于物理内存大小。最大堆内存可通过`-Xmx`参数配置,若没有特别配置,将会使用默认值,详见:[Default Java 8 max heap size](https://stackoverflow.com/questions/28272923/default-xmxsize-in-java-8-max-heap-size))
|
||||
3. ......
|
||||
3. ……
|
||||
|
||||
### 方法区
|
||||
|
||||
|
@ -106,7 +106,7 @@ Oracle 的 HotSpot VM 便附带两个用 C++ 实现的 JIT compiler:C1 及 C2
|
||||
|
||||
- **线程-局部管控**:Java 10 中线程管控引入 JVM 安全点的概念,将允许在不运行全局 JVM 安全点的情况下实现线程回调,由线程本身或者 JVM 线程来执行,同时保持线程处于阻塞状态,这种方式使得停止单个线程变成可能,而不是只能启用或停止所有线程
|
||||
- **备用存储装置上的堆分配**:Java 10 中将使得 JVM 能够使用适用于不同类型的存储机制的堆,在可选内存设备上进行堆内存分配
|
||||
- ......
|
||||
- ……
|
||||
|
||||
## 参考
|
||||
|
||||
|
@ -121,7 +121,7 @@ Consumer<String> consumer = (String i) -> System.out.println(i);
|
||||
- **低开销的 Heap Profiling**:Java 11 中提供一种低开销的 Java 堆分配采样方法,能够得到堆分配的 Java 对象信息,并且能够通过 JVMTI 访问堆信息
|
||||
- **TLS1.3 协议**:Java 11 中包含了传输层安全性(TLS)1.3 规范(RFC 8446)的实现,替换了之前版本中包含的 TLS,包括 TLS 1.2,同时还改进了其他 TLS 功能,例如 OCSP 装订扩展(RFC 6066,RFC 6961),以及会话散列和扩展主密钥扩展(RFC 7627),在安全性和性能方面也做了很多提升
|
||||
- **飞行记录器(Java Flight Recorder)**:飞行记录器之前是商业版 JDK 的一项分析工具,但在 Java 11 中,其代码被包含到公开代码库中,这样所有人都能使用该功能了。
|
||||
- ......
|
||||
- ……
|
||||
|
||||
## 参考
|
||||
|
||||
|
@ -24,7 +24,7 @@ System.out.println(text);
|
||||
|
||||
输出:
|
||||
|
||||
```
|
||||
```plain
|
||||
Java
|
||||
Java
|
||||
```
|
||||
@ -82,7 +82,7 @@ System.out.println(result);
|
||||
|
||||
输出:
|
||||
|
||||
```
|
||||
```plain
|
||||
1K
|
||||
```
|
||||
|
||||
|
@ -182,7 +182,7 @@ System.out.println(encodedString);
|
||||
|
||||
输出:
|
||||
|
||||
```
|
||||
```plain
|
||||
0Hc0lxxASZNvS52WsvnncJOH/mlFhnA8Tc6D/k5DtAX5BSsNVjtPF4R4+yMWXVjrvB2mxVXmChIbki6goFBgAg==
|
||||
```
|
||||
|
||||
@ -238,6 +238,6 @@ Java 15 并没有对此特性进行调整,继续预览特性,主要用于接
|
||||
- **Nashorn JavaScript 引擎彻底移除**:Nashorn 从 Java8 开始引入的 JavaScript 引擎,Java9 对 Nashorn 做了些增强,实现了一些 ES6 的新特性。在 Java 11 中就已经被弃用,到了 Java 15 就彻底被删除了。
|
||||
- **DatagramSocket API 重构**
|
||||
- **禁用和废弃偏向锁(Biased Locking)**:偏向锁的引入增加了 JVM 的复杂性大于其带来的性能提升。不过,你仍然可以使用 `-XX:+UseBiasedLocking` 启用偏向锁定,但它会提示 这是一个已弃用的 API。
|
||||
- ......
|
||||
- ……
|
||||
|
||||
<!-- @include: @article-footer.snippet.md -->
|
@ -18,8 +18,6 @@ JDK 21 共有 15 个新特性:
|
||||
|
||||
- [JEP 440:Record Patterns(记录模式)](https://openjdk.org/jeps/440)
|
||||
|
||||
|
||||
|
||||
## JEP 430:字符串模板(预览)
|
||||
|
||||
String Templates(字符串模板) 目前仍然是 JDK 21 中的一个预览功能。
|
||||
@ -147,4 +145,4 @@ java -XX:+UseZGC -XX:+ZGenerational ...
|
||||
## 参考
|
||||
|
||||
- Java 21 String Templates:<https://howtodoinjava.com/java/java-string-templates/>
|
||||
-
|
||||
|
||||
|
@ -642,7 +642,7 @@ public class MapAndFlatMapExample {
|
||||
|
||||
运行结果:
|
||||
|
||||
```
|
||||
```plain
|
||||
Using map:
|
||||
[[APPLE, BANANA, CHERRY], [ORANGE, GRAPE, PEAR], [KIWI, MELON, PINEAPPLE]]
|
||||
|
||||
|
@ -572,7 +572,7 @@ long millis = TimeUnit.NANOSECONDS.toMillis(t1 - t0);
|
||||
System.out.println(String.format("sequential sort took: %d ms", millis));
|
||||
```
|
||||
|
||||
```
|
||||
```plain
|
||||
1000000
|
||||
sequential sort took: 709 ms//串行排序所用的时间
|
||||
```
|
||||
|
@ -5,7 +5,7 @@ tag:
|
||||
- Java新特性
|
||||
---
|
||||
|
||||
**Java 9** 发布于 2017 年 9 月 21 日 。作为 Java 8 之后 3 年半才发布的新版本,Java 9 带来了很多重大的变化其中最重要的改动是 Java 平台模块系统的引入,其他还有诸如集合、`Stream` 流......。
|
||||
**Java 9** 发布于 2017 年 9 月 21 日 。作为 Java 8 之后 3 年半才发布的新版本,Java 9 带来了很多重大的变化其中最重要的改动是 Java 平台模块系统的引入,其他还有诸如集合、`Stream` 流……。
|
||||
|
||||
你可以在 [Archived OpenJDK General-Availability Releases](http://jdk.java.net/archive/) 上下载自己需要的 JDK 版本!官方的新特性说明文档地址:https://openjdk.java.net/projects/jdk/ 。
|
||||
|
||||
@ -29,14 +29,14 @@ JShell 是 Java 9 新增的一个实用工具。为 Java 提供了类似于 Pyth
|
||||
|
||||
1. 降低了输出第一行 Java 版"Hello World!"的门槛,能够提高新手的学习热情。
|
||||
2. 在处理简单的小逻辑,验证简单的小问题时,比 IDE 更有效率(并不是为了取代 IDE,对于复杂逻辑的验证,IDE 更合适,两者互补)。
|
||||
3. ......
|
||||
3. ……
|
||||
|
||||
**JShell 的代码和普通的可编译代码,有什么不一样?**
|
||||
|
||||
1. 一旦语句输入完成,JShell 立即就能返回执行的结果,而不再需要编辑器、编译器、解释器。
|
||||
2. JShell 支持变量的重复声明,后面声明的会覆盖前面声明的。
|
||||
3. JShell 支持独立的表达式比如普通的加法运算 `1 + 1`。
|
||||
4. ......
|
||||
4. ……
|
||||
|
||||
## 模块化系统
|
||||
|
||||
@ -248,7 +248,7 @@ System.out.println(currentProcess.info());
|
||||
- **I/O 流的新特性**:增加了新的方法来读取和复制 `InputStream` 中包含的数据。
|
||||
- **改进应用的安全性能**:Java 9 新增了 4 个 SHA- 3 哈希算法,SHA3-224、SHA3-256、SHA3-384 和 SHA3-512。
|
||||
- **改进方法句柄(Method Handle)**:方法句柄从 Java7 开始引入,Java9 在类`java.lang.invoke.MethodHandles` 中新增了更多的静态方法来创建不同类型的方法句柄。
|
||||
- ......
|
||||
- ……
|
||||
|
||||
## 参考
|
||||
|
||||
|
Loading…
x
Reference in New Issue
Block a user