...
 
Commits (6)
    https://gitcode.net/qing_gee/jmx-java/-/commit/4b6434c41a85008339e01769843911fbf25beee2 Update hashmap.md 2023-07-25T10:02:48+08:00 itwanger www.qing_gee@163.com https://gitcode.net/qing_gee/jmx-java/-/commit/13d27783b38db38f791f709d906540398252b099 多线程 2023-07-26T17:49:09+08:00 itwanger www.qing_gee@163.com https://gitcode.net/qing_gee/jmx-java/-/commit/0b9fc527bee42ac61f5208c4658ed50192aef145 volatile 重写 2023-07-26T18:44:26+08:00 itwanger www.qing_gee@163.com https://gitcode.net/qing_gee/jmx-java/-/commit/2decb202e8fba49a54844eeb8cf79cd5cb2e3964 synchronized 关键字优化 2023-07-27T10:51:06+08:00 itwanger www.qing_gee@163.com https://gitcode.net/qing_gee/jmx-java/-/commit/8e0aaec3f779f8102e36e775ab54d9a566449d1f 同步 2023-07-28T13:24:14+08:00 itwanger www.qing_gee@163.com https://gitcode.net/qing_gee/jmx-java/-/commit/1a1a6f1b9dbf70a9ab295ad13bf3502aba425b29 Update README.md 2023-07-28T13:57:36+08:00 itwanger www.qing_gee@163.com
......@@ -248,9 +248,10 @@
- [Java线程的6种状态及切换(透彻讲解)](docs/thread/thread-state-and-method.md)
- [线程组是什么?线程优先级如何设置?](docs/thread/thread-group-and-thread-priority.md)
- [进程与线程的区别是什么?](docs/thread/why-need-thread.md)
- [并发编程带来了哪些问题?](docs/thread/thread-bring-some-problem.md)
- [并发编程(多线程)带来了哪些问题?](docs/thread/thread-bring-some-problem.md)
- [全面理解Java的内存模型(JMM)](docs/thread/jmm.md)
- [Java并发编程volatile关键字解析](docs/thread/volatile.md)
- [Java volatile关键字解析](docs/thread/volatile.md)
- [Java synchronized 关键字解析](docs/thread/synchronized-1.md)
- [Java中的synchronized锁的到底是什么?](docs/thread/synchronized.md)
- [Java实现CAS的原理](docs/thread/cas.md)
- [Java并发AQS详解](docs/thread/aqs.md)
......@@ -573,109 +574,6 @@
- [Hippo4J](https://github.com/acmenlt/dynamic-threadpool),🔥 强大的动态线程池,附带监控报警功能(没有依赖中间件),完全遵循阿里巴巴编码规范。
- [JavaGuide](https://github.com/Snailclimb/JavaGuide),「Java学习+面试指南」一份涵盖大部分 Java 程序员所需要掌握的核心知识。准备 Java 面试,首选 JavaGuide!
### 捐赠鼓励
开源不易,如果《二哥的Java进阶之路》对你有些帮助,可以请作者喝杯咖啡,算是对开源做出的一点点鼓励吧!
<div align="left">
<img src="https://itwanger-oss.oss-cn-beijing.aliyuncs.com/tobebetterjavaer/images/weixin-zhifu.png" width="260px">
</div>
:gift_heart: 感谢大家对我资金的赞赏,每隔一个月会统计一次。
时间|小伙伴|赞赏金额
---|---|---
2023-07-02|*晴|1元
2023-06-26|*雨|6.66元
2023-06-21|*航|6元
2023-06-21|*狼|3元
2023-06-19|*定|2元
2023-06-18|*道|5元
2023-06-16|* 文|1元
2023-06-14|G*e|66.6元
2023-06-07|*.|0.5元
2023-05-23|*W|5元
2023-05-19|*飞|6元
2023-05-10|c*r|1元
2023-04-26|r*J|10.24元
2023-04-22|*明|1元
2023-04-09|* 刀|10元
2023-04-03|*意|0.02元
2023-03-17|*昌|8 元
2023-03-16|~*~|66.6 元
2023-03-15|*枫|6.6 元
2023-03-10|十年|1 元
2023-03-04|*风|5 元
2023-02-26|一个表情(emoji)|1 元
2023-02-23|曹*n|5元
2023-02-11|昵称加载中.|6.6元
2023-02-09|*明|10元
2023-02-09|*风|5元
2023-02-09|*z|3元
2023-02-09|*夫|10元
2023-02-08|*宝|5 元
2023-01-18|*念|0.01元
2023-01-18|*来|1元
2023-01-10|*A*t|1元
2023-01-07|*忠|5元
2023-12-02|g*g|0.1元
2022-11-13|*王|5元
2022-11-10|*车|1元
2022-11-10|F*k|1元
2022-11-05|*H|3元
2022-11-04|*金|0.02元
2022-11-04|*尘|15元
2022-11-02|*峰|1元
2022-10-29|~*~|6元
2022-10-28|k*k|1元
2022-10-20|*电|2元
2022-10-15|*深|5元
2022-09-30|*君|1元
2022-09-28|*懂|1元
2022-09-27|*府|1元
2022-09-23|*问号(emogji)|5元
2022-09-23|H*n|1元
2022-09-23|*a|0.01元
2022-09-08|*👀|20元
2022-09-07|丹*1|20元
2022-08-27|*夹|40元
2022-07-06|体*P|2元
2022-07-05|*谦|5元
2022-06-18|*杰|2元
2022-06-15|L*c|15元
2022-06-10|*❤|1元
2022-06-09|'*'|1元
2022-06-07|*勇|1元
2022-06-03|*鸭|1元
2022-05-12|*烟|10元
2022-04-25|*思|5元
2022-04-20|w*n|1元
2022-04-12|E*e|10 元
2022-03-19|*风|9.9元
2022-03-04|袁晓波|99元
2022-02-17|*色|1元
2022-02-17|M*y|1元
2022-01-28|G*R|6.6元
2022-01-20|*光|50元
2022-01-14|*浩|1元
2022-01-01|刚*好|3.6元
2022-01-01|马*谊|6.6元
2021-12-20|t*1|5 元
2021-10-26|*猫|28 元
2021-10-11|*人|28 元
2021-09-28|*人|1 元
2021-09-05|N*a|3 元
2021-09-02|S*n|6.6 元
2021-08-21|z*s|3 元
2021-08-20|A*g|10 元
2021-08-09|*滚|0.1 元
2021-08-02|*秒|1 元
2021-06-13|*7| 28 元
2021-05-04|*学|169 元
2021-04-29|p*e|2 元
2021-04-28|追风筝的神|1 元
### 参与贡献
1. 如果你对本项目有任何建议或发现文中内容有误的,欢迎提交 issues 进行指正。
......
......@@ -116,7 +116,18 @@ public class ReflectionDemo1 {
Class clazz = Class.forName("com.itwanger.s39.Writer");
```
第二步,通过 Class 对象获取构造方法 Constructor 对象:
在 Java 中,Class 对象是一种特殊的对象,它代表了程序中的类和接口。
Java 中的每个类型(包括类、接口、数组以及基础类型)在 JVM 中都有一个唯一的 Class 对象与之对应。这个 Class 对象被创建的时机是在 JVM 加载类时,由 JVM 自动完成。
Class 对象中包含了与类相关的很多信息,如类的名称、类的父类、类实现的接口、类的构造方法、类的方法、类的字段等等。这些信息通常被称为元数据(metadata)。
除了前面提到的,通过类的全名获取 Class 对象,还有以下两种方式:
- 如果你有一个类的实例,你可以通过调用该实例的`getClass()`方法获取 Class 对象。例如:`String str = "Hello World"; Class cls = str.getClass();`
- 如果你有一个类的字面量(即类本身),你可以直接获取 Class 对象。例如:`Class cls = String.class;`
第二步,通过 Class 对象获取构造方法 Constructor 对象:
```java
Constructor constructor = clazz.getConstructor();
......@@ -324,11 +335,11 @@ Method[] methods2 = System.class.getMethods();
第一篇:深入理解 Java 反射和动态代理
>链接:[https://dunwu.github.io/javacore/basics/java-reflection.html](https://dunwu.github.io/javacore/basics/java-reflection.html)
> 链接:[https://dunwu.github.io/javacore/basics/java-reflection.html](https://dunwu.github.io/javacore/basics/java-reflection.html)
第二篇:大白话说Java反射:入门、使用、原理:
第二篇:大白话说 Java 反射:入门、使用、原理:
>链接:[https://www.cnblogs.com/chanshuyi/p/head_first_of_reflection.html](https://www.cnblogs.com/chanshuyi/p/head_first_of_reflection.html)
> 链接:[https://www.cnblogs.com/chanshuyi/p/head_first_of_reflection.html](https://www.cnblogs.com/chanshuyi/p/head_first_of_reflection.html)
这里简单总结下。
......@@ -428,12 +439,10 @@ public class ReflectionDemo {
“好了,三妹,关于反射,就先讲到这里吧。”
----
GitHub 上标星 8700+ 的开源知识库《[二哥的 Java 进阶之路](https://github.com/itwanger/toBeBetterJavaer)》第一版 PDF 终于来了!包括Java基础语法、数组&字符串、OOP、集合框架、Java IO、异常处理、Java 新特性、网络编程、NIO、并发编程、JVM等等,共计 32 万余字,可以说是通俗易懂、风趣幽默……详情戳:[太赞了,GitHub 上标星 8700+ 的 Java 教程](https://javabetter.cn/overview/)
---
GitHub 上标星 8700+ 的开源知识库《[二哥的 Java 进阶之路](https://github.com/itwanger/toBeBetterJavaer)》第一版 PDF 终于来了!包括 Java 基础语法、数组&字符串、OOP、集合框架、Java IO、异常处理、Java 新特性、网络编程、NIO、并发编程、JVM 等等,共计 32 万余字,可以说是通俗易懂、风趣幽默……详情戳:[太赞了,GitHub 上标星 8700+ 的 Java 教程](https://javabetter.cn/overview/)
微信搜 **沉默王二** 或扫描下方二维码关注二哥的原创公众号沉默王二,回复 **222** 即可免费领取。
![](https://cdn.tobebetterjavaer.com/tobebetterjavaer/images/gongzhonghao.png)
......@@ -12,9 +12,11 @@ head:
content: Java,HashMap,java hashmap, 源码分析, 实现原理
---
# 6.9 HashMap详解(附源码)
这篇文章将通过源码的方式,详细透彻地讲清楚 Java 的 HashMap,包括 hash 方法的原理、HashMap 的扩容机制、HashMap的加载因子为什么是 0.75 而不是 0.6、0.8,以及 HashMap 为什么是线程不安全的,基本上 HashMap 的[常见面试题](https://javabetter.cn/interview/java-hashmap-13.html),都会在这一篇文章里讲明白。
这篇文章将通过源码的方式,详细透彻地讲清楚 Java 的 HashMap,包括 hash 方法的原理、HashMap 的扩容机制、HashMap的加载因子为什么是 0.75 而不是 0.6、0.8,以及 HashMap 为什么是线程不安全的,基本上 HashMap 的[常见面试题](https://javabebetter.cn/interview/java-hashmap-13.html),都会在这一篇文章里讲明白。
HashMap 是 Java 中常用的数据结构之一,用于存储键值对。在 HashMap 中,每个键都映射到一个唯一的值,可以通过键来快速访问对应的值。
......@@ -108,7 +110,7 @@ public V put(K key, V value) {
为了方便大家直观的感受,我这里画了一副图,16 个方格子(可以把它想象成一个一个桶),每个格子都有一个编号,对应大小为 16 的数组下标(索引)。
![](https://files.mdnice.com/user/3903/65e8110b-71b1-4146-b2fa-7fc4ee8530af.png)
![](https://cdn.tobebetterjavaer.com/paicoding/3d8ff1f5dc43cc065edb76902156d02b.png)
现在,我们要把 key 为 “chenmo”,value 为“沉默”的键值对放到这 16 个格子中的一个。
......@@ -120,7 +122,7 @@ public V put(K key, V value) {
答案是 8,也就是说 `map.put("chenmo", "沉默")` 会把key 为 “chenmo”,value 为“沉默”的键值对放到下标为 8 的位置上(也就是索引为 8 的桶上)。
![](https://files.mdnice.com/user/3903/e23299c2-a11f-4f0f-a4e5-138fa1fdb331.png)
![](https://cdn.tobebetterjavaer.com/paicoding/fcc9cb8f8252f712d72406f7ffb83a89.png)
这样大家就会对 HashMap 存放键值对(元素)的时候有一个大致的印象。其中的一点是,hash 方法对计算键值对的位置起到了至关重要的作用。
......@@ -338,7 +340,7 @@ hash 方法的原理是,先获取 key 对象的 hashCode 值,然后将其高
好,理解了 hash 方法后我们来看第二个问题,HashMap 的扩容机制。
大家都知道,数组一旦初始化后大小就无法改变了,所以就有了 [ArrayList](https://javabetter.cn/collection/arraylist.html)这种“动态数组”,可以自动扩容。
大家都知道,数组一旦初始化后大小就无法改变了,所以就有了 [ArrayList](https://javabebetter.cn/collection/arraylist.html)这种“动态数组”,可以自动扩容。
HashMap 的底层用的也是数组。向 HashMap 里不停地添加元素,当数组无法装载更多元素时,就需要对数组进行扩容,以便装入更多的元素;除此之外,容量的提升也会相应地提高查询效率,因为“桶(坑)”更多了嘛,原来需要通过链表存储的(查询的时候需要遍历),扩容后可能就有自己专属的“坑位”了(直接就能查出来)。
......@@ -383,7 +385,7 @@ wanger的hash值 : -795084437 的索引 : 11
- fangxiaowan(方小婉)和 yaoxiaojuan(姚小娟)的索引都是 6;
- chenqingyang(陈清扬)和yexin(叶辛)的索引都是 9
这就意味着,要采用拉链法(后面会讲)将他们放在同一个索引的链表上。查询的时候,就不能直接通过索引的方式直接拿到([时间复杂度](https://javabetter.cn/collection/time-complexity.html)为 O(1)),而要通过遍历的方式(时间复杂度为 O(n))。
这就意味着,要采用拉链法(后面会讲)将他们放在同一个索引的链表上。查询的时候,就不能直接通过索引的方式直接拿到([时间复杂度](https://javabebetter.cn/collection/time-complexity.html)为 O(1)),而要通过遍历的方式(时间复杂度为 O(n))。
那假如把数组的长度由 16 扩容为 32 呢?
......@@ -818,9 +820,9 @@ more: less than 1 in ten million
为了便于大家的理解,这里来重温一下 HashMap 的拉链法和红黑树结构。
Java 8 之前,HashMap 使用链表来解决冲突,即当两个或者多个键映射到同一个桶时,它们被放在同一个桶的链表上。当链表上的节点(Node)过多时,链表会变得很长,查找的效率([LinkedList](https://javabetter.cn/collection/linkedlist.html) 的查找效率为 O(n))就会受到影响。
Java 8 之前,HashMap 使用链表来解决冲突,即当两个或者多个键映射到同一个桶时,它们被放在同一个桶的链表上。当链表上的节点(Node)过多时,链表会变得很长,查找的效率([LinkedList](https://javabebetter.cn/collection/linkedlist.html) 的查找效率为 O(n))就会受到影响。
Java 8 中,当链表的节点数超过一个阈值(8)时,链表将转为红黑树(节点为TreeNode),红黑树(在讲[TreeMap](https://javabetter.cn/collection/treemap.html)时会细说)是一种高效的平衡树结构,能够在 O(log n) 的时间内完成插入、删除和查找等操作。这种结构在节点数很多时,可以提高 HashMap 的性能和可伸缩性。
Java 8 中,当链表的节点数超过一个阈值(8)时,链表将转为红黑树(节点为TreeNode),红黑树(在讲[TreeMap](https://javabebetter.cn/collection/treemap.html)时会细说)是一种高效的平衡树结构,能够在 O(log n) 的时间内完成插入、删除和查找等操作。这种结构在节点数很多时,可以提高 HashMap 的性能和可伸缩性。
好,有了这个背景,我们来把上面的 doc 文档翻译为中文:
......@@ -923,7 +925,7 @@ HashMap 的加载因子(load factor,直译为加载因子,意译为负载
### 04、线程不安全
其实这个问题也不用说太多,但考虑到[面试的时候有些面试官会问](https://javabetter.cn/interview/java-hashmap-13.html),那就简单说一下。
其实这个问题也不用说太多,但考虑到[面试的时候有些面试官会问](https://javabebetter.cn/interview/java-hashmap-13.html),那就简单说一下。
三方面原因:
......@@ -1184,25 +1186,25 @@ final Node<K,V>[] resize() {
HashMap 是线程不安全的主要是因为它在进行插入、删除和扩容等操作时可能会导致链表的结构发生变化,从而破坏了 HashMap 的不变性。具体来说,如果在一个线程正在遍历 HashMap 的链表时,另外一个线程对该链表进行了修改(比如添加了一个节点),那么就会导致链表的结构发生变化,从而破坏了当前线程正在进行的遍历操作,可能导致遍历失败或者出现死循环等问题。
为了解决这个问题,Java 提供了线程安全的 HashMap 实现类 [ConcurrentHashMap](https://javabetter.cn/thread/ConcurrentHashMap.html)。ConcurrentHashMap 内部采用了分段锁(Segment),将整个 Map 拆分为多个小的 HashMap,每个小的 HashMap 都有自己的锁,不同的线程可以同时访问不同的小 Map,从而实现了线程安全。在进行插入、删除和扩容等操作时,只需要锁住当前小 Map,不会对整个 Map 进行锁定,提高了并发访问的效率。
为了解决这个问题,Java 提供了线程安全的 HashMap 实现类 [ConcurrentHashMap](https://javabebetter.cn/thread/ConcurrentHashMap.html)。ConcurrentHashMap 内部采用了分段锁(Segment),将整个 Map 拆分为多个小的 HashMap,每个小的 HashMap 都有自己的锁,不同的线程可以同时访问不同的小 Map,从而实现了线程安全。在进行插入、删除和扩容等操作时,只需要锁住当前小 Map,不会对整个 Map 进行锁定,提高了并发访问的效率。
### 05、总结
HashMap是Java中最常用的集合之一,它是一种键值对存储的数据结构,可以根据键来快速访问对应的值。以下是对HashMap的总结:
- HashMap采用数组+链表/红黑树的存储结构,能够在O(1)的时间复杂度内实现元素的添加、删除、查找等操作。
- HashMap是线程不安全的,因此在多线程环境下需要使用[ConcurrentHashMap](https://javabetter.cn/thread/ConcurrentHashMap.html)来保证线程安全。
- HashMap是线程不安全的,因此在多线程环境下需要使用[ConcurrentHashMap](https://javabebetter.cn/thread/ConcurrentHashMap.html)来保证线程安全。
- HashMap的扩容机制是通过扩大数组容量和重新计算hash值来实现的,扩容时需要重新计算所有元素的hash值,因此在元素较多时扩容会影响性能。
- 在Java 8中,HashMap的实现引入了拉链法、树化等机制来优化大量元素存储的情况,进一步提升了性能。
- HashMap中的key是唯一的,如果要存储重复的key,则后面的值会覆盖前面的值。
- HashMap的初始容量和加载因子都可以设置,初始容量表示数组的初始大小,加载因子表示数组的填充因子。一般情况下,初始容量为16,加载因子为0.75。
- HashMap在遍历时是无序的,因此如果需要有序遍历,可以使用[TreeMap](https://javabetter.cn/collection/treemap.html)
- HashMap在遍历时是无序的,因此如果需要有序遍历,可以使用[TreeMap](https://javabebetter.cn/collection/treemap.html)
综上所述,HashMap是一种高效的数据结构,具有快速查找和插入元素的能力,但需要注意线程安全和性能问题。
----
GitHub 上标星 8700+ 的开源知识库《[二哥的 Java 进阶之路](https://github.com/itwanger/toBeBetterJavaer)》第一版 PDF 终于来了!包括Java基础语法、数组&字符串、OOP、集合框架、Java IO、异常处理、Java 新特性、网络编程、NIO、并发编程、JVM等等,共计 32 万余字,可以说是通俗易懂、风趣幽默……详情戳:[太赞了,GitHub 上标星 8700+ 的 Java 教程](https://javabetter.cn/overview/)
GitHub 上标星 8700+ 的开源知识库《[二哥的 Java 进阶之路](https://github.com/itwanger/toBeBetterJavaer)》第一版 PDF 终于来了!包括Java基础语法、数组&字符串、OOP、集合框架、Java IO、异常处理、Java 新特性、网络编程、NIO、并发编程、JVM等等,共计 32 万余字,可以说是通俗易懂、风趣幽默……详情戳:[太赞了,GitHub 上标星 7600+ 的 Java 教程](https://javabebetter.cn/overview/)
微信搜 **沉默王二** 或扫描下方二维码关注二哥的原创公众号沉默王二,回复 **222** 即可免费领取。
......
......@@ -252,15 +252,16 @@ head:
### Java并发编程
- [室友打了一把王者就学会了创建Java线程的3种方式](thread/wangzhe-thread.md)
- [聊聊可以返回结果的创建线程的三个关键类:Callable、Future和FutureTask](thread/callable-future-futuretask.md)
- [创建Java线程的3种方式](thread/wangzhe-thread.md)
- [获取线程的执行结果](thread/callable-future-futuretask.md)
- [Java线程的6种状态及切换(透彻讲解)](thread/thread-state-and-method.md)
- [线程组是什么?线程优先级如何设置?](thread/thread-group-and-thread-priority.md)
- [进程与线程的区别是什么?](thread/why-need-thread.md)
- [并发编程带来了哪些问题?](thread/thread-bring-some-problem.md)
- [全面理解Java的内存模型(JMM)](thread/jmm.md)
- [Java并发编程volatile关键字解析](thread/volatile.md)
- [Java中的synchronized锁的到底是什么?](thread/synchronized.md)
- [线程组和线程优先级](thread/thread-group-and-thread-priority.md)
- [进程与线程的区别](thread/why-need-thread.md)
- [多线程带来了哪些问题?](thread/thread-bring-some-problem.md)
- [Java的内存模型(JMM)](thread/jmm.md)
- [volatile关键字解析](thread/volatile.md)
- [synchronized关键字解析](thread/synchronized-1.md)
- [synchronized锁的到底是什么?](thread/synchronized.md)
- [Java实现CAS的原理](thread/cas.md)
- [Java并发AQS详解](thread/aqs.md)
- [大致了解下Java的锁接口和锁](thread/lock.md)
......
......@@ -48,7 +48,17 @@ head:
引用计数算法是将垃圾回收分摊到整个应用程序的运行当中了,而不是在进行垃圾收集时,要挂起整个应用的运行,直到对堆中所有对象的处理都结束。因此,采用引用计数的垃圾收集不属于严格意义上的"Stop-The-World"的垃圾收集机制。
看似很美好,但我们知道JVM的垃圾回收就是"Stop-The-World"的,那是什么原因导致我们最终放弃了引用计数算法呢?看下面的例子。
"Stop The World"是Java垃圾收集(GC)中的一个重要的概念。在垃圾收集过程中,JVM会暂停所有的用户线程,这种暂停被称为"Stop The World"事件。
这么做的主要原因是为了防止在垃圾收集过程中,用户线程修改了堆中的对象,导致垃圾收集器无法准确地收集垃圾。
值得注意的是,"Stop The World"事件会对Java应用的性能产生影响。如果停顿时间过长,就会导致应用的响应时间变长,对于对实时性要求较高的应用,如交易系统、游戏服务器等,这种情况是不能接受的。
因此,在选择和调优垃圾收集器时,需要考虑其停顿时间。Java中的一些垃圾收集器,如G1和ZGC,尽可能地减少了"Stop The World"的时间,通过并发的垃圾收集,提高了应用的响应性能。
总的来说,"Stop The World"是Java垃圾收集中必须面对的一个挑战,其目标是在保证内存的有效利用和应用的响应性能之间找到一个平衡。
引用计数算法看似很美好,但我们知道JVM的垃圾回收是"Stop-The-World"的,那是什么原因导致我们最终放弃了引用计数算法呢?看下面的例子。
```java
public class ReferenceCountingGC {
......
---
title: 获取 Java 线程执行结果:Callable、Future和FutureTask
shortTitle: 获取线程执行结果
shortTitle: 获取线程执行结果
description: 本文深入解释了如何在Java中使用Callable、Future和FutureTask来获取多线程执行结果。无论你是Java新手还是经验丰富的开发者,你都能在这里找到有用的信息和技巧。点击了解更多。
category:
- Java核心
......@@ -12,7 +12,7 @@ head:
content: Java, 多线程, Callable, Future, FutureTask, 线程执行结果
---
# 14.2 获取线程执行结果
# 14.2 获取线程执行结果
[在第一节:初步掌握 Java 多线程中](https://javabetter.cn/thread/wangzhe-thread.html),我们讲述了创建线程的 3 种方式,一种是直接继承 Thread,一种是实现 Runnable 接口,另外一种是实现 Callable 接口。
......
此差异已折叠。
此差异已折叠。
此差异已折叠。
---
title: 线程组和线程优先级
title: 如何通过线程组管理线程,以及如何设置线程的优先级
shortTitle: 线程组和线程优先级
description: 线程组是什么?线程优先级如何设置?
description: Java 提供了 ThreadGroup 类来创建一组相关的线程,使线程组管理更方便。每个 Java 线程都有一个优先级,这个优先级会影响到操作系统为这个线程分配处理器时间的顺序。
category:
- Java核心
tag:
......@@ -16,9 +16,9 @@ head:
### 线程组(ThreadGroup)
Java用ThreadGroup来表示线程组,我们可以使用线程组对线程进行批量控制。
Java 用 ThreadGroup 来表示线程组,我们可以使用线程组对线程进行批量控制。
ThreadGroup和Thread的关系就如同他们的字面意思一样简单粗暴,每个Thread必然存在于一个ThreadGroup中,Thread不能独立于ThreadGroup存在。执行`main()`方法的线程名字是main,如果在new Thread时没有显式指定,那么默认将父线程(当前执行new Thread的线程)线程组设置为自己的线程组。
ThreadGroup 和 Thread 的关系就如同他们的字面意思一样简单粗暴,每个 Thread 必然存在于一个 ThreadGroup 中,Thread 不能独立于 ThreadGroup 存在。执行`main()`方法的线程名字是 main,如果在 new Thread 时没有显式指定,那么默认将父线程(当前执行 new Thread 的线程)线程组设置为自己的线程组。
示例代码:
......@@ -44,13 +44,13 @@ testThread线程名字:Thread-0
执行main方法线程名字main
```
ThreadGroup是一个标准的**向下引用**的树状结构,这样设计可以**防止"上级"线程被"下级"线程引用而无法有效地被GC回收**
ThreadGroup 是一个标准的**向下引用**的树状结构,这样设计可以**防止"上级"线程被"下级"线程引用而无法有效地被 GC 回收**
### 线程的优先级
线程优先级可以指定,范围是1~10。但并不是所有的操作系统都支持10级优先级的划分(比如有些操作系统只支持3级划分:低、中、高),Java只是给操作系统一个优先级的**参考值**,线程最终**在操作系统中的优先级**还是由操作系统决定。
线程优先级可以指定,范围是 1~10。但并不是所有的操作系统都支持 10 级优先级的划分(比如有些操作系统只支持 3 级划分:低、中、高),Java 只是给操作系统一个优先级的**参考值**,线程最终**在操作系统中的优先级**还是由操作系统决定。
Java默认的线程优先级为5,线程的执行顺序由调度程序来决定,线程的优先级会在线程被调用之前设定。
Java 默认的线程优先级为 5,线程的执行顺序由调度程序来决定,线程的优先级会在线程被调用之前设定。
通常情况下,高优先级的线程将会比低优先级的线程有**更高的概率**得到执行。`Thread`类的`setPriority()`方法可以用来设定线程的优先级。
......@@ -61,6 +61,7 @@ Thread b = new Thread();
b.setPriority(10);
System.out.println("我是设置过的线程优先级:"+b.getPriority());
```
输出结果:
```java
......@@ -68,11 +69,11 @@ System.out.println("我是设置过的线程优先级:"+b.getPriority());
我是设置过的线程优先级10
```
既然有10个级别来设定线程的优先级,那是不是可以在业务实现的时候,采用这种方法来指定线程执行的先后顺序呢?
既然有 10 个级别来设定线程的优先级,那是不是可以在业务实现的时候,采用这种方法来指定线程执行的先后顺序呢?
对于这个问题,答案是:No!
Java中的优先级不是特别的可靠,**Java程序中对线程所设置的优先级只是给操作系统一个建议,操作系统不一定会采纳。而真正的调用顺序,是由操作系统的线程调度算法来决定的**
Java 中的优先级不是特别的可靠,**Java 程序中对线程所设置的优先级只是给操作系统一个建议,操作系统不一定会采纳。而真正的调用顺序,是由操作系统的线程调度算法来决定的**
我们通过代码来验证一下:
......@@ -112,17 +113,17 @@ MyThread当前线程:线程9,优先级:9
MyThread当前线程线程10,优先级10
```
Java提供了一个**线程调度器**来监视和控制处于**RUNNABLE状态**的线程。
Java 提供了一个**线程调度器**来监视和控制处于**RUNNABLE 状态**的线程。
- 线程的调度策略采用**抢占式**的方式,优先级高的线程会比优先级低的线程有更大的几率优先执行。
- 在优先级相同的情况下,会按照“先到先得”的原则执行。
- 每个Java程序都有一个默认的主线程,就是通过JVM启动的第一个线程——main线程。
- 每个 Java 程序都有一个默认的主线程,就是通过 JVM 启动的第一个线程——main 线程。
还有一种特殊的线程,叫做**守护线程(Daemon)**,守护线程默认的优先级比较低。
- 如果某线程是守护线程,那如果所有的非守护线程都结束了,这个守护线程也会自动结束。
- 当所有的非守护线程结束时,守护线程会自动关闭,这就免去了还要继续关闭子线程的麻烦。
- 线程默认是非守护线程,可以通过Thread类的setDaemon方法来设置为守护线程。
- 线程默认是非守护线程,可以通过 Thread 类的 setDaemon 方法来设置为守护线程。
### 线程组和线程优先级之间的关系
......@@ -141,6 +142,7 @@ thread.setPriority(10);
System.out.println("线程组的优先级是:" + group.getMaxPriority());
System.out.println("线程的优先级是:" + thread.getPriority());
```
输出:
```
......@@ -205,11 +207,11 @@ public class ThreadGroup implements Thread.UncaughtExceptionHandler {
boolean destroyed; // 是否被销毁
boolean daemon; // 是否守护线程
boolean vmAllowSuspension; // 是否可以中断
int nUnstartedThreads = 0; // 还未启动的线程
int nthreads; // ThreadGroup中线程数目
Thread threads[]; // ThreadGroup中的线程
int ngroups; // 线程组数目
ThreadGroup groups[]; // 线程组数组
}
......@@ -219,7 +221,7 @@ public class ThreadGroup implements Thread.UncaughtExceptionHandler {
```java
// 私有构造方法
private ThreadGroup() {
private ThreadGroup() {
this.name = "system";
this.maxPriority = Thread.MAX_PRIORITY;
this.parent = null;
......@@ -264,21 +266,20 @@ public final void checkAccess() {
}
```
这里涉及到`SecurityManager`这个类,它是Java的安全管理器,它允许应用程序在执行一个可能不安全或敏感的操作前确定该操作是什么,以及是否是在允许执行该操作的安全上下文中执行它。应用程序可以允许或不允许该操作。
这里涉及到`SecurityManager`这个类,它是 Java 的安全管理器,它允许应用程序在执行一个可能不安全或敏感的操作前确定该操作是什么,以及是否是在允许执行该操作的安全上下文中执行它。应用程序可以允许或不允许该操作。
比如引入了第三方类库,但是并不能保证它的安全性。
其实Thread类也有一个checkAccess方法,不过是用来当前运行的线程是否有权限修改被调用的这个线程实例。(Determines if the currently running thread has permission to modify this thread.)
其实 Thread 类也有一个 checkAccess 方法,不过是用来当前运行的线程是否有权限修改被调用的这个线程实例。(Determines if the currently running thread has permission to modify this thread.)
总结一下,线程组是一个树状的结构,每个线程组下面可以有多个线程或者线程组。线程组可以起到统一控制线程的优先级和检查线程权限的作用。
编辑:沉默王二,原文内容来源于朋友开源的这个仓库:[深入浅出 Java 多线程](http://concurrent.redspider.group/),强烈推荐。
----
GitHub 上标星 8700+ 的开源知识库《[二哥的 Java 进阶之路](https://github.com/itwanger/toBeBetterJavaer)》第一版 PDF 终于来了!包括Java基础语法、数组&字符串、OOP、集合框架、Java IO、异常处理、Java 新特性、网络编程、NIO、并发编程、JVM等等,共计 32 万余字,可以说是通俗易懂、风趣幽默……详情戳:[太赞了,GitHub 上标星 8700+ 的 Java 教程](https://javabetter.cn/overview/)
---
GitHub 上标星 8700+ 的开源知识库《[二哥的 Java 进阶之路](https://github.com/itwanger/toBeBetterJavaer)》第一版 PDF 终于来了!包括 Java 基础语法、数组&字符串、OOP、集合框架、Java IO、异常处理、Java 新特性、网络编程、NIO、并发编程、JVM 等等,共计 32 万余字,可以说是通俗易懂、风趣幽默……详情戳:[太赞了,GitHub 上标星 8700+ 的 Java 教程](https://javabetter.cn/overview/)
微信搜 **沉默王二** 或扫描下方二维码关注二哥的原创公众号沉默王二,回复 **222** 即可免费领取。
![](https://cdn.tobebetterjavaer.com/tobebetterjavaer/images/gongzhonghao.png)
\ No newline at end of file
![](https://cdn.tobebetterjavaer.com/tobebetterjavaer/images/gongzhonghao.png)
---
title: Java并发编程volatile关键字解析
shortTitle: volatile关键字解析
description: Java并发编程volatile关键字解析
title: Java volatile关键字解析
shortTitle: volatile关键字
description: 在 Java 中,volatile 是一种特殊的修饰符,主要用于处理多线程编程中的可见性和有序性问题。
category:
- Java核心
tag:
......@@ -12,41 +12,38 @@ head:
content: Java,并发编程,多线程,Thread,volatile
---
# Java并发编程volatile关键字解析
# 14.8 volatile关键字
“三妹啊,这节我们来学习 Java 并发编程中的 volatile 关键字,以及容易遇到的坑。”看着三妹好学的样子,我倍感欣慰。
>今天这篇换个写作风格。
“三妹啊,这节我们来学习 Java 中的 volatile 关键字吧,以及容易遇到的坑。”看着三妹好学的样子,我倍感欣慰。
“好呀,哥。”三妹愉快的答应了。
## volatile 变量的特性
### volatile 变量的特性
volatile 可以保证可见性,但不保证原子性:
- 当写一个 volatile 变量时,JMM 会把该线程本地内存中的变量强制刷新到主内存中去;
- 当写一个 volatile 变量时,[JMM](https://javabetter.cn/thread/jmm.html) 会把该线程本地内存中的变量强制刷新到主内存中去;
- 这个写操作会导致其他线程中的 volatile 变量缓存无效。
## volatile 禁止指令重排规则
我们回顾一下,重排序需要遵守一定规则:
- 重排序操作不会对存在数据依赖关系的操作进行重排序。比如:a=1;b=a; 这个指令序列,由于第二个操作依赖于第一个操作,所以在编译时和处理器运行时这两个操作不会被重排序。
- 重排序是为了优化性能,但是不管怎么重排序,单线程下程序的执行结果不能被改变。比如:a=1;b=2;c=a+b 这三个操作,第一步(a=1)和第二步(b=2)由于不存在数据依赖关系, 所以可能会发生重排序,但是 c=a+b 这个操作是不会被重排序的,因为需要保证最终的结果一定是 c=a+b=3。
### volatile 会禁止指令重排
使用 volatile 关键字修饰共享变量可以禁止这种重排序。若用 volatile 修饰共享变量,在编译时,会在指令序列中插入内存屏障来禁止特定类型的处理器重排序,volatile 禁止指令重排序也有一些规则:
我们回顾一下,重排序需要遵守的规则:
- 当程序执行到 volatile 变量的读操作或者写操作时,在其前面的操作的更改肯定全部已经进行,且结果已经对后面的操作可见;在其后面的操作肯定还没有进行;
- 在进行指令优化时,不能将对 volatile 变量访问的语句放在其后面执行,也不能把 volatile 变量后面的语句放到其前面执行
- 重排序不会对存在数据依赖关系的操作进行重排序。比如:`a=1;b=a;` 这个指令序列,因为第二个操作依赖于第一个操作,所以在编译时和处理器运行时这两个操作不会被重排序。
- 重排序是为了优化性能,但是不管怎么重排序,单线程下程序的执行结果不能被改变。比如:`a=1;b=2;c=a+b` 这三个操作,第一步 (a=1) 和第二步 (b=2) 由于不存在数据依赖关系,所以可能会发生重排序,但是 c=a+b 这个操作是不会被重排序的,因为需要保证最终的结果一定是 c=a+b=3
“二哥,能不能通俗地讲讲啊?”
使用 volatile 关键字修饰共享变量可以禁止这种重排序。如果用 volatile 修饰共享变量,在编译时,会在指令序列中插入内存屏障来禁止重排序,规则如下:
“也就是说,执行到 volatile 变量时,其前面的所有语句都执行完,后面所有语句都未执行。且前面语句的结果对 volatile 变量及其后面语句可见。”我瞅了了三妹一眼说。
- 当程序执行到 volatile 变量的读操作或者写操作时,在其前面操作的更改肯定已经全部进行,且结果对后面的操作可见;在其后面的操作肯定还没有进行;
- 在进行指令优化时,不能将 volatile 变量的语句放在其后面执行,也不能把 volatile 变量后面的语句放到其前面执行。
## volatile 禁止指令重排分析
“也就是说,执行到 volatile 变量时,其前面的所有语句都必须执行完,后面所有得语句都未执行。且前面语句的结果对 volatile 变量及其后面语句可见。”我瞅了了三妹一眼继续说。
先看下面未使用 volatile 的代码:
```
```java
class ReorderExample {
  int a = 0;
  boolean flag = false;
......@@ -63,9 +60,9 @@ class ReorderExample {
}
```
因为重排序影响,所以最终的输出可能是 0,具体分析请参考[上一篇](https://mp.weixin.qq.com/s/s983WflPH7jF0-_SpGRfBg),如果引入 volatile,我们再看一下代码:
因为重排序影响,所以最终的输出可能是 0,具体分析请参考[上一篇](https://javabetter.cn/thread/jmm.html),如果引入 volatile,我们再看一下代码:
```
```java
class ReorderExample {
  int a = 0;
  boolean volatile flag = false;
......@@ -82,12 +79,14 @@ class ReorderExample {
}
```
这个时候,volatile 禁止指令重排序也有一些规则,这个过程建立的 happens before 关系可以分为两类
这个时候,volatile 禁止指令重排序也有一些规则,这个过程建立的 [happens before 关系](https://javabetter.cn/thread/jmm.html) 如下
1. 根据程序次序规则,1 happens before 2; 3 happens before 4。
2. 根据 volatile 规则,2 happens before 3。
3. 根据 happens before 的传递性规则,1 happens before 4。
>在 Java 中,"happens-before" 是一个用于描述两个或多个操作之间的偏序关系的概念,它来自于 Java 内存模型 ([上一篇](https://javabetter.cn/thread/jmm.html)有详细讲)。"happens-before" 关系可以确保在并发环境中的内存可见性,即一个线程中的写操作对于另一个线程中的读操作是可见的。
上述 happens before 关系的图形化表现形式如下:
![](https://cdn.tobebetterjavaer.com/tobebetterjavaer/images/thread/volatile-f4de7989-672e-43d6-906b-feffe4fb0a9c.jpg)
......@@ -100,13 +99,13 @@ class ReorderExample {
这里 A 线程写一个 volatile 变量后,B 线程读同一个 volatile 变量。A 线程在写 volatile 变量之前所有可见的共享变量,在 B 线程读同一个 volatile 变量后,将立即变得对 B 线程可见。
## volatile 不适用场景
### volatile 不适用的场景
### volatile 不适合复合操作
#### 复合操作
下面是变量自加的示例:
```
```java
public class volatileTest {
    public volatile int inc = 0;
    public void increase() {
......@@ -135,17 +134,17 @@ public class volatileTest {
inc output:8182
```
“为什么呀?二哥?”三妹疑惑地问。
“为什么呀?二哥?” 看到这个结果,三妹疑惑地问。
“因为 inc++不是一个原子性操作,由读取、加、赋值 3 步组成,所以结果并不能达到 10000。”我耐心地回答。
“因为 inc++不是一个[原子性操作](https://javabetter.cn/thread/thread-bring-some-problem.html)(前面讲过),由读取、加、赋值 3 步组成,所以结果并不能达到 10000。”我耐心地回答。
“哦,你这样说我就理解了。”三妹点点头。
### 解决方法
#### 解决方法
采用 synchronized
采用 [synchronized](https://javabetter.cn/thread/synchronized-1.html),把 `inc++` 拎出来单独加 synchronized 关键字
```
```java
public class volatileTest1 {
    public int inc = 0;
    public synchronized void increase() {
......@@ -168,9 +167,9 @@ public class volatileTest1 {
}
```
采用 Lock
采用 [Lock](https://javabetter.cn/thread/suo.html),通过重入锁 [ReentrantLock](https://javabetter.cn/thread/reentrantLock.html)`inc++` 加锁
```
```java
public class volatileTest2 {
    public int inc = 0;
    Lock lock = new ReentrantLock();
......@@ -196,9 +195,9 @@ public class volatileTest2 {
}
```
采用 AtomicInteger
采用原子类 [AtomicInteger](https://javabetter.cn/thread/atomic.html) 来实现
```
```java
public class volatileTest3 {
    public AtomicInteger inc = new AtomicInteger();
    public void increase() {
......@@ -229,11 +228,11 @@ add lock, inc output:1000
add AtomicInteger, inc output:1000
```
## 单例模式的双重锁要加volatile
### 单例模式的双重锁与volatile
先看一下单例代码:
这是一个使用"双重检查锁定"(double-checked locking)实现的单例模式(Singleton Pattern)的例子。
```
```java
public class penguin {
    private static volatile penguin m_penguin = null;
    // 避免通过new初始化对象
......@@ -254,9 +253,28 @@ public class penguin {
}
```
并发情况下,如果没有 volatile 关键字,在第 5 行会出现问题。instance = new TestInstance();可以分解为 3 行伪代码:
这个例子中,penguin 类只能被实例化一次。
```
首先看代码解释:
- 声明了一个类型为 penguin 的 volatile 变量 m_penguin,它是类的静态变量,用来存储 penguin 类的唯一实例。
- `penguin()` 构造方法被声明为 private,这样就阻止了外部代码使用 new 来创建 penguin 实例,保证了只能通过 `getInstance()` 方法获取实例。
- `getInstance()` 方法是获取 penguin 类唯一实例的公共静态方法。
- `if (null == m_penguin)` 检查是否已经存在实例。如果不存在,才进入同步代码块。
- `synchronized(penguin.class)` 对类的 Class 对象加锁,这是确保在多线程环境下,同时只能有一个线程进入同步代码块。在同步代码块中,再次检查实例是否已经存在,如果不存在,则创建新的实例。这就是所谓的"双重检查锁定"。
- 最后返回 m_penguin,也就是 penguin 的唯一实例。
其中,使用 volatile 关键字是为了防止 `m_penguin = new penguin()` 这一步被指令重排序。实际上,`new penguin()` 这一步分为三个子步骤:
- 分配对象的内存空间。
- 初始化对象。
- 将 m_penguin 指向分配的内存空间。
如果不使用 volatile 关键字,JVM 可能会对这三个子步骤进行指令重排序,如果步骤2和步骤3被重排序,那么线程A可能在对象还没有被初始化完成时,线程B已经开始使用这个对象,从而导致问题。而使用 volatile 关键字可以防止这种指令重排序。
伪代码代码如下:
```java
a. memory = allocate() //分配内存
b. ctorInstanc(memory) //初始化对象
c. instance = memory   //设置instance指向刚分配的地址
......@@ -266,19 +284,21 @@ c. instance = memory   //设置instance指向刚分配的地址
当线程 A 在执行第 5 行代码时,B 线程进来执行到第 2 行代码。假设此时 A 执行的过程中发生了指令重排序,即先执行了 a 和 c,没有执行 b。那么由于 A 线程执行了 c 导致 instance 指向了一段地址,所以 B 线程判断 instance 不为 null,会直接跳到第 6 行并返回一个未初始化的对象。
##
### 小
“好了,三妹,我们来总结一下。”我舒了一口气说。
volatile 可以保证线程可见性且提供了一定的有序性,但是无法保证原子性。在 JVM 底层 volatile 是采用“内存屏障”来实现的。
观察加入 volatile 关键字和没有加入 volatile 关键字时所生成的汇编代码发现,加入 volatile 关键字时,会多出一个 lock 前缀指令,lock 前缀指令实际上相当于一个内存屏障(也称内存栅栏),内存屏障会提供 3 个功能:
观察加入 volatile 关键字和没有加入 volatile 关键字时所生成的汇编代码就能发现,加入 volatile 关键字时,会多出一个 lock 前缀指令,lock 前缀指令实际上相当于一个内存屏障(也称内存栅栏),内存屏障会提供 3 个功能:
- 它确保指令重排序时不会把其后面的指令排到内存屏障之前的位置,也不会把前面的指令排到内存屏障的后面;即在执行到内存屏障这句指令时,在它前面的操作已经全部完成;
- 它会强制将对缓存的修改操作立即写入主存;
- 如果是写操作,它会导致其他 CPU 中对应的缓存行无效。
最后,我们学习了 volatile 不适用的场景,以及解决的方法,并解释了单例模式为何需要使用 volatile。
最后,我们学习了 volatile 不适用的场景,以及解决的方法,并解释了双重检查锁定实现的单例模式为何需要使用 volatile。
>编辑:沉默王二,编辑前的内容主要来自于二哥的[技术派](https://paicoding.com/)团队成员楼仔,原文链接戳:[volatile](https://paicoding.com/column/4/2)。
----
......
---
title: 室友打了一把王者就学会了 Java 多线程
shortTitle: 初步掌握 Java 多线程
shortTitle: 创建Java线程的3种方式
category:
- Java核心
tag:
......@@ -12,7 +12,7 @@ head:
content: Java,并发编程,多线程,Thread
---
# 14.1 初步掌握 Java 多线程
# 14.1 创建Java线程的3种方式
对于 Java 初学者来说,多线程的很多概念听起来就很难理解。比方说:
......
此差异已折叠。