1
0
mirror of https://github.com/Snailclimb/JavaGuide synced 2025-06-16 18:10:13 +08:00

[docs update]Java并发部分内容重构完善

This commit is contained in:
guide 2022-08-04 21:00:03 +08:00
parent c58d5c3547
commit dffc5dd94a
41 changed files with 428 additions and 141 deletions

View File

@ -90,11 +90,12 @@
**重要知识点详解**
- [JMMJava 内存模型)详解](./docs/java/concurrent/jmm.md)
- **线程池** [Java 线程池详解](./docs/java/concurrent/java-thread-pool-summary.md)、[Java 线程池最佳实践](./docs/java/concurrent/java-thread-pool-best-practices.md)
- [ThreadLocal 关键字解析](docs/java/concurrent/threadlocal.md)
- [ThreadLocal 详解](docs/java/concurrent/threadlocal.md)
- [Java 并发容器总结](docs/java/concurrent/java-concurrent-collections.md)
- [Atomic 原子类总结](docs/java/concurrent/atomic-classes.md)
- [AQS 原理以及 AQS 同步组件总结](docs/java/concurrent/aqs.md)
- [AQS 详解](docs/java/concurrent/aqs.md)
- [CompletableFuture入门](docs/java/concurrent/completablefuture-intro.md)
### JVM (必看 :+1:)

View File

@ -104,11 +104,11 @@ export const sidebarConfig = defineSidebarConfig({
icon: "star",
collapsable: true,
children: [
"jmm",
"java-thread-pool-summary",
"java-thread-pool-best-practices",
"java-concurrent-collections",
"aqs",
"reentrantlock",
"atomic-classes",
"threadlocal",
"completablefuture-intro",

View File

@ -7,6 +7,7 @@ head:
- - meta
- name: keywords
content: 操作系统,进程,进程通信方式,死锁,操作系统内存管理,块表,多级页表,虚拟内存,页面置换算法
- - meta
- name: description
content: 很多读者抱怨计算操作系统的知识点比较繁杂,自己也没有多少耐心去看,但是面试的时候又经常会遇到。所以,我带着我整理好的操作系统的常见问题来啦!这篇文章总结了一些我觉得比较重要的操作系统相关的问题比如进程管理、内存管理、虚拟内存等等。
---

View File

@ -8,6 +8,7 @@ head:
- - meta
- name: keywords
content: MySQL基础,MySQL基础架构,MySQL存储引擎,MySQL查询缓存,MySQL事务,MySQL锁等内容。
- - meta
- name: description
content: 一篇文章总结MySQL常见的知识点和面试题涵盖MySQL基础、MySQL基础架构、MySQL存储引擎、MySQL查询缓存、MySQL事务、MySQL锁等内容。
---

View File

@ -7,6 +7,7 @@ head:
- - meta
- name: keywords
content: Redis常见数据结构
- - meta
- name: description
content: Redis基础数据结构总结String字符串、List列表、Set集合、Hash散列、Zset有序集合
---

View File

@ -7,6 +7,7 @@ head:
- - meta
- name: keywords
content: Redis常见数据结构
- - meta
- name: description
content: Redis特殊数据结构总结HyperLogLogs基数统计、Bitmap 位存储、Geospatial (地理位置)。
---

View File

@ -7,6 +7,7 @@ head:
- - meta
- name: keywords
content: Redis基础,Redis常见数据结构,Redis线程模型,Redis内存管理,Redis事务,Redis性能优化
- - meta
- name: description
content: 一篇文章总结Redis常见的知识点和面试题涵盖Redis基础、Redis常见数据结构、Redis线程模型、Redis内存管理、Redis事务、Redis性能优化等内容。
---

View File

@ -7,6 +7,7 @@ head:
- - meta
- name: keywords
content: Redis基础,Redis常见数据结构,Redis线程模型,Redis内存管理,Redis事务,Redis性能优化
- - meta
- name: description
content: 一篇文章总结Redis常见的知识点和面试题涵盖Redis基础、Redis常见数据结构、Redis线程模型、Redis内存管理、Redis事务、Redis性能优化等内容。
---

View File

@ -5,6 +5,7 @@ head:
- - meta
- name: keywords
content: 客户端负载均衡,服务负载均衡,Nginx,负载均衡算法,七层负载均衡,DNS解析
- - meta
- name: description
content: 负载均衡指的是将用户请求分摊到不同的服务器上处理,以提高系统整体的并发处理能力。负载均衡可以简单分为服务端负载均衡和客户端负载均衡 这两种。服务端负载均衡涉及到的知识点更多,工作中遇到的也比较多,因为,我会花更多时间来介绍。
---

View File

@ -7,6 +7,7 @@ head:
- - meta
- name: keywords
content: RabbitMQ,AMQP,Broker,Exchange,优先级队列,延迟队列
- - meta
- name: description
content: RabbitMQ 是一个在 AMQPAdvanced Message Queuing Protocol 基础上实现的可复用的企业消息系统。它可以用于大型软件系统各个模块之间的高效通信支持高并发支持可扩展。它支持多种客户端如Python、Ruby、.NET、Java、JMS、C、PHP、ActionScript、XMPP、STOMP等支持AJAX持久化用于在分布式系统中存储转发消息在易用性、扩展性、高可用性等方面表现不俗。
---

View File

@ -7,6 +7,7 @@ head:
- - meta
- name: keywords
content: JVM,JDK,JRE,字节码详解,Java 基本数据类型,装箱和拆箱
- - meta
- name: description
content: 全网质量最高的Java基础常见知识点和面试题总结希望对你有帮助
---

View File

@ -7,6 +7,7 @@ head:
- - meta
- name: keywords
content: 面向对象,构造方法,接口,抽象类,String,Object
- - meta
- name: description
content: 全网质量最高的Java基础常见知识点和面试题总结希望对你有帮助
---

View File

@ -7,6 +7,7 @@ head:
- - meta
- name: keywords
content: Java异常,泛型,反射,IO,注解
- - meta
- name: description
content: 全网质量最高的Java基础常见知识点和面试题总结希望对你有帮助
---

View File

@ -7,6 +7,7 @@ head:
- - meta
- name: keywords
content: Java SPI机制
- - meta
- name: description
content: SPI 即 Service Provider Interface 字面意思就是“服务提供者的接口”我的理解是专门提供给服务提供者或者扩展框架功能的开发者去使用的一个接口。SPI 将服务接口和具体的服务实现分离开来,将服务调用方和服务实现者解耦,能够提升程序的扩展性、可维护性。修改或者替换服务实现并不需要修改调用方。
---

View File

@ -6,7 +6,8 @@ tag:
head:
- - meta
- name: keywords
content: Collection,List,Set,Queue,Deque,PriorityQueue,
content: Collection,List,Set,Queue,Deque,PriorityQueue
- - meta
- name: description
content: Java集合常见知识点和面试题总结希望对你有帮助
---

View File

@ -7,6 +7,7 @@ head:
- - meta
- name: keywords
content: HashMap,ConcurrentHashMap,Hashtable,List,Set
- - meta
- name: description
content: Java集合常见知识点和面试题总结希望对你有帮助
---

View File

@ -1,5 +1,5 @@
---
title: AQS 原理&AQS 同步组件总结
title: AQS 详解
category: Java
tag:
- Java并发
@ -13,6 +13,8 @@ tag:
- 用过 `Semaphore` 吗?应用场景了解吗?
- ......
相关阅读:[从 ReentrantLock 的实现看AQS的原理及应用](./reentrantlock.md)
## AQS 简单介绍
AQS 的全称为 `AbstractQueuedSynchronizer` ,翻译过来的意思就是抽象队列同步器。这个类在 `java.util.concurrent.locks` 包下面。

View File

@ -1 +1 @@
<mxfile host="Electron" modified="2022-01-24T01:30:38.715Z" agent="5.0 (Macintosh; Intel Mac OS X 10_16_0) AppleWebKit/537.36 (KHTML, like Gecko) draw.io/13.4.5 Chrome/83.0.4103.122 Electron/9.1.0 Safari/537.36" etag="l1tJc7jft47xw4GR_2dM" version="13.4.5" type="device"><diagram id="f1uOqe90rjhexKzs8PuL" name="Page-1">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</diagram></mxfile>
<mxfile host="Electron" modified="2022-08-04T12:45:10.835Z" agent="5.0 (Macintosh; Intel Mac OS X 10_16_0) AppleWebKit/537.36 (KHTML, like Gecko) draw.io/13.4.5 Chrome/83.0.4103.122 Electron/9.1.0 Safari/537.36" etag="WotcJiYPt2iKRm6Hc-5O" version="13.4.5" type="device"><diagram id="f1uOqe90rjhexKzs8PuL" name="Page-1">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</diagram></mxfile>

Binary file not shown.

After

Width:  |  Height:  |  Size: 22 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 22 KiB

View File

@ -1 +1 @@
<mxfile host="Electron" modified="2022-01-24T01:31:02.213Z" agent="5.0 (Macintosh; Intel Mac OS X 10_16_0) AppleWebKit/537.36 (KHTML, like Gecko) draw.io/13.4.5 Chrome/83.0.4103.122 Electron/9.1.0 Safari/537.36" etag="tG8e46NqnXu62oEqxvIZ" version="13.4.5" type="device"><diagram id="l-JW3fph1e97gRfsgveQ" name="Page-1">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</diagram></mxfile>
<mxfile host="Electron" modified="2022-08-04T12:45:04.137Z" agent="5.0 (Macintosh; Intel Mac OS X 10_16_0) AppleWebKit/537.36 (KHTML, like Gecko) draw.io/13.4.5 Chrome/83.0.4103.122 Electron/9.1.0 Safari/537.36" etag="bDDGEj_wTsAIpHJuuoFW" version="13.4.5" type="device"><diagram id="l-JW3fph1e97gRfsgveQ" name="Page-1">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</diagram></mxfile>

Binary file not shown.

After

Width:  |  Height:  |  Size: 21 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 21 KiB

View File

Before

Width:  |  Height:  |  Size: 9.9 KiB

After

Width:  |  Height:  |  Size: 9.9 KiB

View File

@ -0,0 +1 @@
<mxfile host="Electron" modified="2022-08-04T12:39:26.711Z" agent="5.0 (Macintosh; Intel Mac OS X 10_16_0) AppleWebKit/537.36 (KHTML, like Gecko) draw.io/13.4.5 Chrome/83.0.4103.122 Electron/9.1.0 Safari/537.36" etag="owfxTT7KcwZ98QLN75Wc" version="13.4.5" type="device"><diagram id="XZHCjfYPnYWvLepVL7Sq" name="Page-1">7Vtdc6M2FP01PCbDhxH40XacdNpsZ9vstJu+ySAb1jJiQY7t/vpKQhjzZeME7DRhkhmjKyHQPedcXYRQjMlq+xDB0PtCXIQVXXW3inGn6LqmqYD9cMsusQyHVmJYRL4rG2WGJ/9fJI2qtK59F8W5hpQQTP0wb3RIECCH5mwwisgm32xOcP6qIVygkuHJgbhs/dt3qZeOCwyzil+Qv/DSSwNzkNSsYNpaDiX2oEs2ByZjqhiTiBCaHK22E4S591LHJOfd19Tu7yxCAW1ygr755+fz79HXUbzGy/lv85vJ+o8bO+nlBeK1HLG8WbpLXRCRdeAi3ommGOON51P0FEKH124Y6Mzm0RWW1fESUceThTkJqISU3RurTD3AC+XblyN6QRFF2wOTHM4DIitEox1rImuNgXStJJeumUl5k0Glp228A5QGtjRCSY/Fvu/MgexA+vAMfxplf05NxTaV0USZDpTxRGEC4JZ7ZcwO7oVl9Ct8gQ+c6YoOMLvH8SzKYQB+rjlLhENvYuHREWuggXCbVbKjhfidWsr4Thlq/GA0VobD/XV+sOsIRd06QQln5nRaAJNGZIkmBJOIWQISIH4PPsYFE8T+ImBFh0GJmH3MIfSZiEayYuW7Lr9MJXsyfnFaYDhDeAyd5ULYixfPGKUBWU6bKLoxFn+iTwqpT/i1bwy1yMu2eWjaeR4OVLXEQ82u4KHWFQ21Kl0n1OIua0Quu4pc37wIQTfti91b0p1k3vukVAsID4w8wns0DxAGFQDrXQEMwIUCt5BZCY1DxJjuXIjsuVOCl9UAx0azeTsgWGoh3KdT7alwb4OOULDUktORy/IHWSQR9ciCBBBPM2uBnFmbR0JC6f8fiNKdBACuKclDVUQnB12tn/mNHfVyhDCLmS/5xKfKZfLUr8QXkaQGHWAWuE9htEBUnnWYpxQ6MvUTHcVkHTmo1JFAcD+e14M6GJ6WFptBQn5I4UxgeoBOlc5iNvYUMg46y1Yp9AMuJXGOQzCGYeyLzpIWno/dR7gja5peJi2N87o+X4qv488ZwbKoU6tiOtSr0rLO5sOUQk0wZWOnPsR/sscJGCyawFuGz41I+E3wXRpCzlYUTV+YW2Npq5j2qAgBIg9C8/TcGaGUrGQhku7adypcZY7ZPxvjRL01FZONZsLKWlZm/7x5xJKlgBED+gJcBGO6QTGthP24Nk6T4RTYpt0V1loF1mfmPmZV7jMNxO01S33ewibxKAszNhHm7jkWGavH0h4UtEGehojvlfOeEdd7dV9Y3bs8pKegB50F9ooHbqnQOIRBe2JPevvoYtcbE+B6iA96sV9H7JrWUO3dBXqzV3uLah80ZsD1EG+wzvGp1V67LlZas+kuLljg2nHBKrHk9vb24woXNIboepA0eLP0qYXbmRx1o7wsemHsq1bQ+mn6tWq3GzPgaoiDfn3tWmof6s3U3tkDGahfb+vVfrbaQXvrbd0h3q+3XUnthtlQ7d1F+n7BrU21t7fg1hnillZy/7kvuXNvGF/xxhtxbnyXnYnCMy8wNcri3faw8m6XxoTCm84yKunYxCvlY0mY9DhNw1AtovLZrL3X7W8Dr0Go/uj7RIDacFvgsKsJ06oPmm1sx3okDsRfGFKffVvWHtVrbcuyWligPgU0b9N0nnwfSB+NGR0IXretcpZkXpQHVcvWrfMAwBV3djCLxU9PihwptMIsUBEbNOOipCivUpczq8Ad8c8WMp833fiHtj79nmVFrPR8UJMlSLyQ5ke1bj6ZvRw4sUpYqe2NewrtwgZ/MDTzXdRsBTzdkTrMd1SzObGtPYVW/X7st8WDv3iH/XSQU75tnZ4OqtK/7oTfwnp4Pfqstg/9eQKk+wSOhH7rkgSwq5ZI+8h/NPLnIdx/6nV+5M93tP9o4EKR3656AG/j0W+JGu9FfR+qbyOnKz7vGVd+3rNbWA2tRDfp8H8V1dsI3OBi+LJi9ultIvbsC2Zj+h8=</diagram></mxfile>

Binary file not shown.

After

Width:  |  Height:  |  Size: 23 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 79 KiB

View File

@ -0,0 +1 @@
<mxfile host="Electron" modified="2022-08-04T02:57:48.322Z" agent="5.0 (Macintosh; Intel Mac OS X 10_16_0) AppleWebKit/537.36 (KHTML, like Gecko) draw.io/13.4.5 Chrome/83.0.4103.122 Electron/9.1.0 Safari/537.36" etag="ce8Ll6ISaXkxNGjUtgQW" version="13.4.5" type="device"><diagram id="O1q-3gN19tnn94MyZq-a" name="Page-1">7VnbcqM4EP0aPcYFCGF4xLekpia12fVMzeVlSgHFaIORB+TbfP1IIK4iGWcTNq5K7FSMjoSE+pzuhgbA6fpwmeJNdM1CEgPLCA8AzoBlmabhiB+JHAvE88YFsEppqAbVwJL+Igo0FLqlIclaAzljMaebNhiwJCEBb2E4Tdm+PeyOxe1VN3hFNGAZ4FhHv9CQRwXqWuMavyJ0FZUrm45X9KxxOVjtJItwyPYNCM4BnKaM8eJofZiSWBqvtEtx3uKB3urCUpLwU0749Gt5HV6x/VXwc/lX+DH7gj64F1Yxyw7HW7VhdbH8WFogZdskJHISE8DJPqKcLDc4kL17wbnAIr6OVfcdS7giUVyOaNM4nrKYpflc8M4NSBAIPOMpuyeNnlsX2Uiekd0THkRquspmskffsLLBjqScHBqQMsAlYWvC06MYUuuxOEWpsZLZvubWshUWNXn1FIiVnlbV3LXJxYGy+hMYgBoD05vPAviHrGjGSZo9k5A2AcQMERn3EeA5Y4idLgENPk13CEJQmxDYQ4jp9BAyHooPc6y7xBwBFwF/CuY2mEyBCGESWYCJOFjkiP8B7/CljFXAcmJxjZPbtMWa83MrHT036EWWW9QXA0xnc6g7xdEq/52PwWQGPFMe+BPgedU6/4p18pg4ChJNGcLovE1/m+aEJaSjCQXhmK4S0QwElUTgE0khFWHQVx1rGoZymV691YqUsojxLYknOLhf5Xh38YaiHNVuyHCSf/M5OeaUybUv4OCBodKT0qEDe3To9gWGwXTo9QeGs6S8G/aHDeJuJ4j3cWX0cAWH4qpM8W8ljdoQnVsarTbxNvOoPW4z8vp5tHSB9/jV5QoZHe95/fj10E3oFAcReZ7jaA4CHejBUHepxcKf2YtHHKdgZuAnAts7NZQNlvkt+82y0U0s58AG+nNuJ0noy1qDjEYxzjJa5Gacch1+JMuTA+VfpTFHyPJU+1veNoyyPTsoa+eNY6NxQ1IqtixD4ex0bjK2TQNyQmwgYatQolPYoAj1MFRiKYnFff2uXV7pY02tcMOouOT6Rr3jr8joEF9sSJ3VLId0J4LjttTKqFxOJOhbEa5NlIuo2vYzdOW8hq6aqnpUUS+lHvus1IOQPXogwjxVP/lUdkdC9sgw/18V6dWKa0wTgVyTNVPhb4BscXpW0PLK3HLRMLnbdfRsgfpuegfMFvpT+1BefZqHnonjVfeuZX3lv7qdZXldt3O6KWBgnyu38s6xFhFfhmOE4KjzPOsY1qh8c/TyNGO4OWY/6EeDfvru/r1zv/Hvmwu9oJCXZafAg7L868+A5+YFYRe4BpiL/6LXBnNHNv2iRDyTVWLR5c/l3/Ni8ckRN8RZVM3arRwh+e0L7U7+0QuyxWeYgO303N4PF7B7WdarFO++LGDoPpBan+rL2kQvFqxFs355WgyvX0HD+W8=</diagram></mxfile>

Binary file not shown.

After

Width:  |  Height:  |  Size: 31 KiB

View File

Before

Width:  |  Height:  |  Size: 72 KiB

After

Width:  |  Height:  |  Size: 72 KiB

View File

Before

Width:  |  Height:  |  Size: 80 KiB

After

Width:  |  Height:  |  Size: 80 KiB

View File

Before

Width:  |  Height:  |  Size: 51 KiB

After

Width:  |  Height:  |  Size: 51 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 40 KiB

View File

@ -7,6 +7,7 @@ head:
- - meta
- name: keywords
content: 线程和进程,并发和并行,多线程,死锁,线程的生命周期
- - meta
- name: description
content: Java并发常见知识点和面试题总结含详细解答希望对你有帮助
---

View File

@ -7,76 +7,30 @@ head:
- - meta
- name: keywords
content: 多线程,死锁,synchronized,ReentrantLock,volatile,ThreadLocal,线程池,CAS,AQS
- - meta
- name: description
content: Java并发常见知识点和面试题总结含详细解答希望对你有帮助
---
## JMM(Java Memory Model)
JMMJava 内存模型)相关的问题比较多,也比较重要,于是我单独抽了一篇文章来总结 JMM 相关的知识点和问题: [JMMJava 内存模型)详解](./jmm.md) 。
## volatile 关键字
要想理解透彻 volatile 关键字,我们先要从 **CPU 缓存模型** 说起!
### CPU 缓存模型了解吗?
**为什么要弄一个 CPU 高速缓存呢?** 类比我们开发网站后台系统使用的缓存(比如 Redis是为了解决程序处理速度和访问常规关系型数据库速度不对等的问题。 **CPU 缓存则是为了解决 CPU 处理速度和内存处理速度不对等的问题。**
我们甚至可以把 **内存可以看作外存的高速缓存**,程序运行的时候我们把外存的数据复制到内存,由于内存的处理速度远远高于外存,这样提高了处理速度。
总结:**CPU Cache 缓存的是内存数据用于解决 CPU 处理速度和内存不匹配的问题,内存缓存的是硬盘数据用于解决硬盘访问速度过慢的问题。**
为了更好地理解,我画了一个简单的 CPU Cache 示意图如下(实际上,现代的 CPU Cache 通常分为三层,分别叫 L1,L2,L3 Cache:
![cpu-cache](./images/interview-questions/cpu-cache.png)
**CPU Cache 的工作方式:**
先复制一份数据到 CPU Cache 中,当 CPU 需要用到的时候就可以直接从 CPU Cache 中读取数据,当运算完成后,再将运算得到的数据写回 Main Memory 中。但是,这样存在 **内存缓存不一致性的问题** !比如我执行一个 i++操作的话,如果两个线程同时执行的话,假设两个线程从 CPU Cache 中读取的 i=1两个线程做了 1++运算完之后再写回 Main Memory 之后 i=2而正确结果应该是 i=3。
**CPU 为了解决内存缓存不一致性问题可以通过制定缓存一致协议或者其他手段来解决。**
### 讲一下 JMM(Java 内存模型)
Java 内存模型抽象了线程和主内存之间的关系就比如说线程之间的共享变量必须存储在主内存中。Java 内存模型主要目的是为了屏蔽系统和硬件的差异,避免一套代码在不同的平台下产生的效果不一致。
在 JDK1.2 之前Java 的内存模型实现总是从**主存**(即共享内存)读取变量,是不需要进行特别的注意的。而在当前的 Java 内存模型下,线程可以把变量保存**本地内存**(比如机器的寄存器)中,而不是直接在主存中进行读写。这就可能造成一个线程在主存中修改了一个变量的值,而另外一个线程还继续使用它在寄存器中的变量值的拷贝,造成**数据的不一致**。
> - **主内存** :所有线程创建的实例对象都存放在主内存中,不管该实例对象是成员变量还是方法中的本地变量(也称局部变量)
> - **本地内存** :每个线程都有一个私有的本地内存来存储共享变量的副本,并且,每个线程只能访问自己的本地内存,无法访问其他线程的本地内存。本地内存是 JMM 抽象出来的一个概念,存储了主内存中的共享变量副本。
![JMM(Java 内存模型)](./images/interview-questions/jmm.png)
### Java 内存区域和内存模型(JMM)有何区别?
**Java 内存区域和内存模型是完全不一样的两个东西!!!**
- Java 内存区域定义了JVM 在运行时如何分区存储程序数据,就比如说堆主要用于存放对象实例。
- Java 内存模型抽象了线程和主内存之间的关系,就比如说线程之间的共享变量必须存储在主内存中,其目的是为了屏蔽系统和硬件的差异,避免一套代码在不同的平台下产生的效果不一致。
### 如何保证变量的可见性?
`volatile` 关键字可以保证变量的可见性,如果我们将变量声明为 **`volatile`** ,这就指示 JVM这个变量是共享且不稳定的每次使用它都到主存中进行读取。
在 Java 中,`volatile` 关键字可以保证变量的可见性,如果我们将变量声明为 **`volatile`** ,这就指示 JVM这个变量是共享且不稳定的每次使用它都到主存中进行读取。
![volatile关键字可见性](./images/interview-questions/jmm2.png)
![JMM(Java 内存模型)](./images/jmm/jmm.png)
### 什么是指令重排序?如何禁止指令重排序?
`volatile` 关键字其实并非是 Java 语言特有的,在 C 语言里也有,它最原始的意义就是禁用 CPU 缓存。如果我们将一个变量使用 `volatile` 修饰,这就指示 编译器,这个变量是共享且不稳定的,每次使用它都到主存中进行读取。
为了提升执行速度/性能,计算机在执行程序代码的时候,会对指令就行重排序
`volatile` 关键字能保证数据的可见性,但不能保证数据的原子性。`synchronized` 关键字两者都能保证。
指令重排序简单来说就是系统在执行代码的时候并不一定是按照你写的代码的顺序依次执行。
### 如何禁止指令重排序?
常见的重排序有下面 2 种情况:
- **编译器优化重排** :编译器(包括 JVM、JIT 编译器等)在不改变单线程程序语义的前提下,重新安排语句的执行顺序。
- **指令并行重排** :和编译器优化重排类似,
另外,内存系统也会有“重排序”,但有不是真正意义上的重排序。在 JMM 里表现为主存和本地内存,而主存和本地内存的内容可能不一致,所以这也会导致程序表现出乱序的行为。
**指令重排序可以保证串行语义一致,但是没有义务保证多线程间的语义也一致**。所以在多线程下,指令重排序可能会导致一些问题。
**`volatile` 关键字除了可以保证变量的可见性,还有一个重要的作用就是防止 JVM 的指令重排序。**
如果我们将变量声明为 **`volatile`** ,在对这个变量进行读写操作的时候,编译器会通过 **内存屏障** 来禁止指令重排序。
内存屏障Memory Barrier或有时叫做内存栅栏Memory Fence是一种CPU指令用于控制特定条件下的重排序和内存可见性问题。Java编译器也会根据内存屏障的规则禁止重排序。
**在 Java 中,`volatile` 关键字除了可以保证变量的可见性,还有一个重要的作用就是防止 JVM 的指令重排序。** 如果我们将变量声明为 **`volatile`** ,在对这个变量进行读写操作的时候,会通过插入特定的 **内存屏障** 的方式来禁止指令重排序。
在 Java 中,`Unsafe` 类提供了三个开箱即用的内存屏障相关的方法,屏蔽了操作系统底层的差异:
@ -86,7 +40,135 @@ public native void storeFence();
public native void fullFence();
```
理论上来说,你通过这个三个方法也可以实现和`volatile`禁止重排序一样的效果。
理论上来说,你通过这个三个方法也可以实现和`volatile`禁止重排序一样的效果,只是会麻烦一些。
下面我以一个常见的面试题为例讲解一下 `volatile` 关键字禁止指令重排序的效果。
面试中面试官经常会说:“单例模式了解吗?来给我手写一下!给我解释一下双重检验锁方式实现单例模式的原理呗!”
**双重校验锁实现对象单例(线程安全)**
```java
public class Singleton {
private volatile static Singleton uniqueInstance;
private Singleton() {
}
public static Singleton getUniqueInstance() {
//先判断对象是否已经实例过,没有实例化过才进入加锁代码
if (uniqueInstance == null) {
//类对象加锁
synchronized (Singleton.class) {
if (uniqueInstance == null) {
uniqueInstance = new Singleton();
}
}
}
return uniqueInstance;
}
}
```
`uniqueInstance` 采用 `volatile` 关键字修饰也是很有必要的, `uniqueInstance = new Singleton();` 这段代码其实是分为三步执行:
1. 为 `uniqueInstance` 分配内存空间
2. 初始化 `uniqueInstance`
3. 将 `uniqueInstance` 指向分配的内存地址
但是由于 JVM 具有指令重排的特性,执行顺序有可能变成 1->3->2。指令重排在单线程环境下不会出现问题但是在多线程环境下会导致一个线程获得还没有初始化的实例。例如线程 T1 执行了 1 和 3此时 T2 调用 `getUniqueInstance`() 后发现 `uniqueInstance` 不为空,因此返回 `uniqueInstance`,但此时 `uniqueInstance` 还未被初始化。
### volatile 可以保证原子性么?
**`volatile` 关键字能保证变量的可见性,但不能保证对变量的操作是原子性的。**
我们通过下面的代码即可证明:
```java
/**
* 微信搜 JavaGuide 回复"面试突击"即可免费领取个人原创的 Java 面试手册
*
* @author Guide哥
* @date 2022/08/03 13:40
**/
public class VolatoleAtomicityDemo {
public volatile static int inc = 0;
public void increase() {
inc++;
}
public static void main(String[] args) throws InterruptedException {
ExecutorService threadPool = Executors.newFixedThreadPool(5);
VolatoleAtomicityDemo volatoleAtomicityDemo = new VolatoleAtomicityDemo();
for (int i = 0; i < 5; i++) {
threadPool.execute(() -> {
for (int j = 0; j < 500; j++) {
volatoleAtomicityDemo.increase();
}
});
}
// 等待1.5秒,保证上面程序执行完成
Thread.sleep(1500);
System.out.println(inc);
threadPool.shutdown();
}
}
```
正常情况下,运行上面的代码理应输出 `2500`。但你真正运行了上面的代码之后,你会发现每次输出结果都小于 `2500`
为什么会出现这种情况呢?不是说好了,`volatile` 可以保证变量的可见性嘛!
也就是说,如果 `volatile` 能保证 `inc++` 操作的原子性的话。每个线程中对 `inc` 变量自增完之后其他线程可以立即看到修改后的值。5 个线程分别进行了 500 次操作,那么最终 inc 的值应该是 5\*500=2500。
很多人会误认为自增操作 `inc++` 是原子性的,实际上,`inc++` 其实是一个复合操作,包括三步:
1. 读取 inc 的值。
2. 对 inc 加 1。
3. 将 inc 的值写回内存。
`volatile` 是无法保证这三个操作是具有原子性的,有可能导致下面这种情况出现:
1. 线程 1 对 `inc` 进行读取操作之后,还未对其进行修改。线程 2 又读取了 `inc`的值并对其进行修改(+1再将`inc` 的值写回内存。
2. 线程 2 操作完毕后,线程 1 对 `inc`的值进行修改(+1再将`inc` 的值写回内存。
这也就导致两个线程分别对 `inc` 进行了一次自增操作后,`inc` 实际上只增加了 1。
其实,如果想要保证上面的代码运行正确也非常简单,利用 `synchronized``Lock`或者`AtomicInteger`都可以。
使用 `synchronized` 改进:
```java
public synchronized void increase() {
inc++;
}
```
使用 `AtomicInteger` 改进:
```java
public AtomicInteger inc = new AtomicInteger();
public void increase() {
inc.getAndIncrement();
}
```
使用 `ReentrantLock` 改进:
```java
Lock lock = new ReentrantLock();
public void increase() {
lock.lock();
try {
inc++;
} finally {
lock.unlock();
}
}
```
## synchronized 关键字
@ -149,47 +231,6 @@ synchronized(this) {
- `synchronized` 关键字加到实例方法上是给对象实例上锁;
- 尽量不要使用 `synchronized(String a)` 因为 JVM 中,字符串常量池具有缓存功能。
下面我以一个常见的面试题为例讲解一下 `synchronized` 关键字的具体使用。
面试中面试官经常会说:“单例模式了解吗?来给我手写一下!给我解释一下双重检验锁方式实现单例模式的原理呗!”
**双重校验锁实现对象单例(线程安全)**
```java
public class Singleton {
private volatile static Singleton uniqueInstance;
private Singleton() {
}
public static Singleton getUniqueInstance() {
//先判断对象是否已经实例过,没有实例化过才进入加锁代码
if (uniqueInstance == null) {
//类对象加锁
synchronized (Singleton.class) {
if (uniqueInstance == null) {
uniqueInstance = new Singleton();
}
}
}
return uniqueInstance;
}
}
```
另外,需要注意 `uniqueInstance` 采用 `volatile` 关键字修饰也是很有必要。
`uniqueInstance` 采用 `volatile` 关键字修饰也是很有必要的, `uniqueInstance = new Singleton();` 这段代码其实是分为三步执行:
1. 为 `uniqueInstance` 分配内存空间
2. 初始化 `uniqueInstance`
3. 将 `uniqueInstance` 指向分配的内存地址
但是由于 JVM 具有指令重排的特性,执行顺序有可能变成 1->3->2。指令重排在单线程环境下不会出现问题但是在多线程环境下会导致一个线程获得还没有初始化的实例。例如线程 T1 执行了 1 和 3此时 T2 调用 `getUniqueInstance`() 后发现 `uniqueInstance` 不为空,因此返回 `uniqueInstance`,但此时 `uniqueInstance` 还未被初始化。
使用 `volatile` 可以禁止 JVM 的指令重排,保证在多线程环境下也能正常运行。
### 构造方法可以使用 synchronized 关键字修饰么?
先说结论:**构造方法不能使用 synchronized 关键字修饰。**
@ -227,13 +268,11 @@ public class SynchronizedDemo {
在执行`monitorenter`时,会尝试获取对象的锁,如果锁的计数器为 0 则表示锁可以被获取,获取后将锁计数器设为 1 也就是加 1。
![执行 monitorenter 获取锁](./images/interview-questions/synchronized-get-lock-code-block.png)
![执行 monitorenter 获取锁](./images/interview-questions/synchronized-get-lock-code-block.jpg)
对象锁的的拥有者线程才可以执行 `monitorexit` 指令来释放锁。在执行 `monitorexit` 指令后,将锁计数器设为 0表明锁被释放其他线程可以尝试获取锁。
![执行 monitorexit 释放锁](./images/interview-questions/synchronized-release-lock-block.png)
![执行 monitorexit 释放锁](./images/interview-questions/synchronized-release-lock-block.jpg)
如果获取对象锁失败,那当前线程就要阻塞等待,直到锁被另外一个线程释放为止。
@ -262,9 +301,9 @@ public class SynchronizedDemo2 {
**不过两者的本质都是对对象监视器 monitor 的获取。**
相关推荐:[Java锁与线程的那些事 - 有赞技术团队](https://tech.youzan.com/javasuo-yu-xian-cheng-de-na-xie-shi/) 。
相关推荐:[Java 锁与线程的那些事 - 有赞技术团队](https://tech.youzan.com/javasuo-yu-xian-cheng-de-na-xie-shi/) 。
🧗🏻进阶一下:学有余力的小伙伴可以抽时间详细研究一下对象监视器 `monitor`
🧗🏻 进阶一下:学有余力的小伙伴可以抽时间详细研究一下对象监视器 `monitor`
### JDK1.6 之后的 synchronized 关键字底层做了哪些优化?
@ -278,7 +317,7 @@ JDK1.6 对锁的实现引入了大量的优化,如偏向锁、轻量级锁、
`synchronized` 关键字和 `volatile` 关键字是两个互补的存在,而不是对立的存在!
- `volatile` 关键字是线程同步的轻量级实现,所以 `volatile `性能肯定比` synchronized `关键字要好 。但是 `volatile` 关键字只能用于变量而 `synchronized` 关键字可以修饰方法以及代码块 。
- `volatile` 关键字是线程同步的轻量级实现,所以 `volatile`性能肯定比`synchronized`关键字要好 。但是 `volatile` 关键字只能用于变量而 `synchronized` 关键字可以修饰方法以及代码块 。
- `volatile` 关键字能保证数据的可见性,但不能保证数据的原子性。`synchronized` 关键字两者都能保证。
- `volatile`关键字主要用于解决变量在多个线程之间的可见性,而 `synchronized` 关键字解决的是多个线程之间访问资源的同步性。
@ -304,27 +343,21 @@ JDK1.6 对锁的实现引入了大量的优化,如偏向锁、轻量级锁、
**如果你想使用上述功能,那么选择 ReentrantLock 是一个不错的选择。性能已不是选择标准**
### 并发编程的三个重要特性
1. **原子性** : 一次操作或者多次操作,要么所有的操作全部都得到执行并且不会受到任何因素的干扰而中断,要么都不执行。`synchronized` 可以保证代码片段的原子性。
2. **可见性** :当一个线程对共享变量进行了修改,那么另外的线程都是立即可以看到修改后的最新值。`volatile` 关键字可以保证共享变量的可见性。
3. **有序性** 代码在执行的过程中的先后顺序Java 在编译器以及运行期间的优化,代码的执行顺序未必就是编写代码时候的顺序。`volatile` 关键字可以禁止指令进行重排序优化。
## ThreadLocal
### ThreadLocal 简介
### ThreadLocal 有什么用?
通常情况下,我们创建的变量是可以被任何一个线程访问并修改的。**如果想实现每一个线程都有自己的专属本地变量该如何解决呢?** JDK 中提供的`ThreadLocal`类正是为了解决这样的问题。 **`ThreadLocal`类主要解决的就是让每个线程绑定自己的值,可以将`ThreadLocal`类形象的比喻成存放数据的盒子,盒子中可以存储每个线程的私有数据。**
通常情况下,我们创建的变量是可以被任何一个线程访问并修改的。**如果想实现每一个线程都有自己的专属本地变量该如何解决呢?**
**如果你创建了一个`ThreadLocal`变量,那么访问这个变量的每个线程都会有这个变量的本地副本,这也是`ThreadLocal`变量名的由来。他们可以使用 `get``set` 方法来获取默认值或将其值更改为当前线程所存的副本的值,从而避免了线程安全问题。**
JDK 中自带的`ThreadLocal`类正是为了解决这样的问题。 **`ThreadLocal`类主要解决的就是让每个线程绑定自己的值,可以将`ThreadLocal`类形象的比喻成存放数据的盒子,盒子中可以存储每个线程的私有数据。**
再举个简单的例子:
如果你创建了一个`ThreadLocal`变量,那么访问这个变量的每个线程都会有这个变量的本地副本,这也是`ThreadLocal`变量名的由来。他们可以使用 `get``set` 方法来获取默认值或将其值更改为当前线程所存的副本的值,从而避免了线程安全问题。
比如有两个人去宝屋收集宝物,这两个共用一个袋子的话肯定会产生争执,但是给他们两个人每个人分配一个袋子的话就不会出现这样的问题。如果把这两个人比作线程的话,那么 ThreadLocal 就是用来避免这两个线程竞争的。
再举个简单的例子:两个人去宝屋收集宝物,这两个共用一个袋子的话肯定会产生争执,但是给他们两个人每个人分配一个袋子的话就不会出现这样的问题。如果把这两个人比作线程的话,那么 ThreadLocal 就是用来避免这两个线程竞争的。
### ThreadLocal 示例
### 如何使用 ThreadLocal
相信看了上面的解释,大家已经搞懂 ThreadLocal 类是个什么东西了。
相信看了上面的解释,大家已经搞懂 `ThreadLocal` 类是个什么东西了。下面简单演示一下如何在项目中实际使用 `ThreadLocal`
```java
import java.text.SimpleDateFormat;
@ -362,7 +395,7 @@ public class ThreadLocalExample implements Runnable{
```
Output:
输出结果 :
```
Thread Name= 0 default Formatter = yyyyMMdd HHmm
@ -387,7 +420,7 @@ Thread Name= 8 formatter = yy-M-d ah:mm
Thread Name= 9 formatter = yy-M-d ah:mm
```
从输出中可以看出,Thread-0 已经改变了 formatter 的值,但仍然是 thread-2 默认格式化程序与初始化值相同,其他线程也一样。
从输出中可以看出,虽然 `Thread-0` 已经改变了 `formatter` 的值,但 `Thread-1` 默认格式化值与初始化值相同,其他线程也一样。
上面有一段代码用到了创建 `ThreadLocal` 变量的那段代码用到了 Java8 的知识它等于下面这段代码如果你写了下面这段代码的话IDEA 会提示你转换为 Java8 的格式(IDEA 真的不错!)。因为 ThreadLocal 类在 Java 8 中扩展,使用一个新的方法`withInitial()`,将 Supplier 功能接口作为参数。
@ -400,7 +433,7 @@ private static final ThreadLocal<SimpleDateFormat> formatter = new ThreadLocal<S
};
```
### ThreadLocal 原理
### ThreadLocal 原理了解吗?
`Thread`类源代码入手。
@ -446,15 +479,19 @@ ThreadLocalMap(ThreadLocal<?> firstKey, Object firstValue) {
比如我们在同一个线程中声明了两个 `ThreadLocal` 对象的话, `Thread`内部都是使用仅有的那个`ThreadLocalMap` 存放数据的,`ThreadLocalMap`的 key 就是 `ThreadLocal`对象value 就是 `ThreadLocal` 对象调用`set`方法设置的值。
![ThreadLocal数据结构](./images/threadlocal数据结构.png)
`ThreadLocal` 数据结构如下图所示:
![threadlocal-data-structure](images/interview-questions/threadlocal-data-structure.jpg)
`ThreadLocalMap``ThreadLocal`的静态内部类。
![ThreadLocal内部类](./images/ThreadLocal内部类.png)
![ThreadLocal内部类](./images/interview-questions/thread-local-inner-class.png)
### ThreadLocal 内存泄露问题
### ThreadLocal 内存泄露问题是怎么导致的?
`ThreadLocalMap` 中使用的 key 为 `ThreadLocal` 的弱引用,而 value 是强引用。所以,如果 `ThreadLocal` 没有被外部强引用的情况下在垃圾回收的时候key 会被清理掉,而 value 不会被清理掉。这样一来,`ThreadLocalMap` 中就会出现 key 为 null 的 Entry。假如我们不做任何措施的话value 永远无法被 GC 回收这个时候就可能会产生内存泄露。ThreadLocalMap 实现中已经考虑了这种情况,在调用 `set()``get()``remove()` 方法的时候,会清理掉 key 为 null 的记录。使用完 `ThreadLocal`方法后 最好手动调用`remove()`方法
`ThreadLocalMap` 中使用的 key 为 `ThreadLocal` 的弱引用,而 value 是强引用。所以,如果 `ThreadLocal` 没有被外部强引用的情况下在垃圾回收的时候key 会被清理掉,而 value 不会被清理掉。
这样一来,`ThreadLocalMap` 中就会出现 key 为 null 的 Entry。假如我们不做任何措施的话value 永远无法被 GC 回收,这个时候就可能会产生内存泄露。`ThreadLocalMap` 实现中已经考虑了这种情况,在调用 `set()``get()``remove()` 方法的时候,会清理掉 key 为 null 的记录。使用完 `ThreadLocal`方法后 最好手动调用`remove()`方法
```java
static class Entry extends WeakReference<ThreadLocal<?>> {
@ -478,9 +515,6 @@ static class Entry extends WeakReference<ThreadLocal<?>> {
- 《深入理解 Java 虚拟机》
- 《实战 Java 高并发程序设计》
- 《Java 并发编程的艺术》
- 《深入浅出Java多线程》http://concurrent.redspider.group/RedSpider.html
- Java内存访问重排序的研究https://tech.meituan.com/2014/09/23/java-memory-reordering.html
- 嘿,同学,你要的 Java 内存模型 (JMM) 来了https://xie.infoq.cn/article/739920a92d0d27e2053174ef2
- Java并发之AQS详解https://www.cnblogs.com/waterystone/p/4920797.html
- Java并发包基石-AQS详解https://www.cnblogs.com/chengxiao/archive/2017/07/24/7141160.html
- Guide to the Volatile Keyword in Java - Baeldunghttps://www.baeldung.com/java-volatile
- 理解 Java 中的 ThreadLocal - 技术小黑屋https://droidyue.com/blog/2016/03/13/learning-threadlocal-in-java/
- ThreadLocal (Java Platform SE 8 ) - Oracle Help Centerhttps://docs.oracle.com/javase/8/docs/api/java/lang/ThreadLocal.html

View File

@ -7,6 +7,7 @@ head:
- - meta
- name: keywords
content: 多线程,死锁,线程池,CAS,AQS
- - meta
- name: description
content: Java并发常见知识点和面试题总结含详细解答希望对你有帮助
---

232
docs/java/concurrent/jmm.md Normal file
View File

@ -0,0 +1,232 @@
---
title: JMMJava 内存模型)详解
category: Java
tag:
- Java并发
head:
- - meta
- name: keywords
content: CPU 缓存模型,指令重排序,Java 内存模型JMM,happens-before
- - meta
- name: description
content: 对于 Java 来说,你可以把 JMM 看作是 Java 定义的并发编程相关的一组规范,除了抽象了线程和主内存之间的关系之外,其还规定了从 Java 源代码到 CPU 可执行指令的这个转化过程要遵守哪些和并发相关的原则和规范,其主要目的是为了简化多线程编程,增强程序可移植性的。
---
要想理解透彻 JMMJava 内存模型),我们先要从 **CPU 缓存模型和指令重排序** 说起!
## 从 CPU 缓存模型说起
**为什么要弄一个 CPU 高速缓存呢?** 类比我们开发网站后台系统使用的缓存(比如 Redis是为了解决程序处理速度和访问常规关系型数据库速度不对等的问题。 **CPU 缓存则是为了解决 CPU 处理速度和内存处理速度不对等的问题。**
我们甚至可以把 **内存可以看作外存的高速缓存**,程序运行的时候我们把外存的数据复制到内存,由于内存的处理速度远远高于外存,这样提高了处理速度。
总结:**CPU Cache 缓存的是内存数据用于解决 CPU 处理速度和内存不匹配的问题,内存缓存的是硬盘数据用于解决硬盘访问速度过慢的问题。**
为了更好地理解,我画了一个简单的 CPU Cache 示意图如下(实际上,现代的 CPU Cache 通常分为三层,分别叫 L1,L2,L3 Cache:
![CPU 缓存模型示意图](./images/jmm/cpu-cache.png)
**CPU Cache 的工作方式:** 先复制一份数据到 CPU Cache 中,当 CPU 需要用到的时候就可以直接从 CPU Cache 中读取数据,当运算完成后,再将运算得到的数据写回 Main Memory 中。但是,这样存在 **内存缓存不一致性的问题** !比如我执行一个 i++操作的话,如果两个线程同时执行的话,假设两个线程从 CPU Cache 中读取的 i=1两个线程做了 1++运算完之后再写回 Main Memory 之后 i=2而正确结果应该是 i=3。
**CPU 为了解决内存缓存不一致性问题可以通过制定缓存一致协议(比如 [MESI 协议](https://zh.wikipedia.org/wiki/MESI%E5%8D%8F%E8%AE%AE))或者其他手段来解决。** 这个缓存缓存一致性协议指的是在 CPU 高速缓存与主内存交互的时候需要准守的原则和规范。不同的 CPU 中,使用的缓存一致性协议通常也会有所不同。
![缓存一致性协议](./images/jmm/cpu-cache-protocol.jpg)
我们的程序运行在操作系统之上,操作系统屏蔽了底层硬件的操作细节,将各种硬件资源虚拟化。于是,操作系统也就同样需要解决内存缓存不一致性问题。
操作系统通过 **内存模型Memory Model** 定义一系列规范来解决这个问题。无论是 Windows 系统,还是 Linux 系统,它们都有特定的内存模型。
## 指令重排序
说完了 CPU 缓存模型,我们再来看看另外一个比较重要的概念 **指令重排序** ,这同样
为了提升执行速度/性能,计算机在执行程序代码的时候,会对指令进行重排序。
**什么是指令重排序?** 简单来说就是系统在执行代码的时候并不一定是按照你写的代码的顺序依次执行。
常见的指令重排序有下面 2 种情况:
- **编译器优化重排** :编译器(包括 JVM、JIT 编译器等)在不改变单线程程序语义的前提下,重新安排语句的执行顺序。
- **指令并行重排** :现代处理器采用了指令级并行技术(Instruction-Level ParallelismILP)来将多条指令重叠执行。如果不存在数据依赖性,处理器可以改变语句对应机器指令的执行顺序。
另外,内存系统也会有“重排序”,但有不是真正意义上的重排序。在 JMM 里表现为主存和本地内存的内容可能不一致,进而导致程序在多线程下执行可能出现问题。
Java 源代码会经历 **编译器优化重排 —> 指令并行重排 —> 内存系统重排** 的过程,最终才变成操作系统可执行的指令序列。
**指令重排序可以保证串行语义一致,但是没有义务保证多线程间的语义也一致** ,所以在多线程下,指令重排序可能会导致一些问题。
编译器和处理器的指令重排序的处理方式不一样。对于编译器通过禁止特定类型的编译器的当时来禁止重排序。对于处理器通过插入内存屏障Memory Barrier或有时叫做内存栅栏Memory Fence的方式来禁止特定类型的处理器重排序。指令并行重排和内存系统重排都属于是处理器级别的指令重排序。
> 内存屏障Memory Barrier或有时叫做内存栅栏Memory Fence是一种 CPU 指令,用来禁止处理器指令发生重排序(像屏障一样),从而保障指令执行的有序性。另外,为了达到屏障的效果,它也会使处理器写入、读取值之前,将主内存的值写入高速缓存,清空无效队列,从而保障变量的可见性。
## JMM(Java Memory Model)
### 什么是 JMM为什么需要 JMM
Java 是最早尝试提供内存模型的编程语言。由于早期内存模型存在一些缺陷(比如非常容易削弱编译器的优化能力),从 Java5 开始Java 开始使用新的内存模型 [《JSR-133Java Memory Model and Thread Specification》](http://www.cs.umd.edu/~pugh/java/memoryModel/CommunityReview.pdf) 。
一般来说编程语言也可以直接复用操作系统层面的内存模型。不过不同的操作系统内存模型不同。如果直接复用操作系统层面的内存模型就可能会导致同样一套代码换了一个操作系统就无法执行了。Java 语言是跨平台的,它需要自己提供一套内存模型以屏蔽系统差异。
这只是 JMM 存在的其中一个原因。实际上,对于 Java 来说,你可以把 JMM 看作是 Java 定义的并发编程相关的一组规范,除了抽象了线程和主内存之间的关系之外,其还规定了从 Java 源代码到 CPU 可执行指令的这个转化过程要遵守哪些和并发相关的原则和规范,其主要目的是为了简化多线程编程,增强程序可移植性的。
**为什么要遵守这些并发相关的原则和规范呢?** 这是因为并发编程下,像 CPU 多级缓存和指令重排这类设计可能会导致程序运行出现一些问题。就比如说我们上面提到的指令重排序就可能会让多线程程序的执行出现问题为此JMM 抽象了 happens-before 原则(后文会详细介绍到)来解决这个指令重排序问题。
JMM 说白了就是定义了一些规范来解决这些问题,开发发者可以利用这些规范更方便地开发多线程程序。对于 Java 开发者说,你不需要了解底层原理,直接使用并发相关的一些关键字和类(比如 `volatile``synchronized`、各种 `Lock`)即可开发出并发安全的程序。
### JMM 是如何抽象线程和主内存之间的关系?
**Java 内存模型JMM** 抽象了线程和主内存之间的关系,就比如说线程之间的共享变量必须存储在主内存中。
在 JDK1.2 之前Java 的内存模型实现总是从 **主存** (即共享内存)读取变量,是不需要进行特别的注意的。而在当前的 Java 内存模型下,线程可以把变量保存 **本地内存** (比如机器的寄存器)中,而不是直接在主存中进行读写。这就可能造成一个线程在主存中修改了一个变量的值,而另外一个线程还继续使用它在寄存器中的变量值的拷贝,造成数据的不一致。
这和我们上面讲到的 CPU 缓存模型非常相似。
**什么是主内存?什么是本地内存?**
- **主内存** :所有线程创建的实例对象都存放在主内存中,不管该实例对象是成员变量还是方法中的本地变量(也称局部变量)
- **本地内存** :每个线程都有一个私有的本地内存来存储共享变量的副本,并且,每个线程只能访问自己的本地内存,无法访问其他线程的本地内存。本地内存是 JMM 抽象出来的一个概念,存储了主内存中的共享变量副本。
Java 内存模型的抽象示意图如下:
![JMM(Java 内存模型)](./images/jmm/jmm.png)
从上图来看,线程 1 与线程 2 之间如果要进行通信的话,必须要经历下面 2 个步骤:
1. 线程 1 把本地内存中修改过的共享变量副本的值同步到主内存中去。
2. 线程 2 到主存中读取对应的共享变量的值。
也就是说JMM 为共享变量提供了可见性的保障。
不过,多线程下,对主内存中的一个共享变量进行操作有可能诱发线程安全问题。举个例子:
1. 线程 1 和线程 2 分别对同一个共享变量进行操作,一个执行修改,一个执行读取。
2. 线程 2 读取到的是线程 1 修改之前的值还是修改后的值并不确定,都有可能,因为线程 1 和线程 2 都是先将共享变量从主内存拷贝到对应线程的工作内存中。
关于主内存与工作内存直接的具体交互协议即一个变量如何从主内存拷贝到工作内存如何从工作内存同步到主内存之间的实现细节Java 内存模型定义来以下八种同步操作(了解即可,无需死记硬背):
- **锁定lock**: 作用于主内存中的变量,将他标记为一个线程独享变量。
- **解锁unlock**: 作用于主内存中的变量,解除变量的锁定状态,被解除锁定状态的变量才能被其他线程锁定。
- **read读取**:作用于主内存的变量,它把一个变量的值从主内存传输到线程的工作内存中,以便随后的 load 动作使用。
- **load(载入)**:把 read 操作从主内存中得到的变量值放入工作内存的变量的副本中。
- **use(使用)**:把工作内存中的一个变量的值传给执行引擎,每当虚拟机遇到一个使用到变量的指令时都会使用该指令。
- **assign赋值**:作用于工作内存的变量,它把一个从执行引擎接收到的值赋给工作内存的变量,每当虚拟机遇到一个给变量赋值的字节码指令时执行这个操作。
- **store存储**:作用于工作内存的变量,它把工作内存中一个变量的值传送到主内存中,以便随后的 write 操作使用。
- **write写入**:作用于主内存的变量,它把 store 操作从工作内存中得到的变量的值放入主内存的变量中。
除了这 8 种同步操作之外,还规定了下面这些同步规则来保证这些同步操作的正确执行(了解即可,无需死记硬背):
- 不允许一个线程无原因地(没有发生过任何 assign 操作)把数据从线程的工作内存同步回主内存中。
- 一个新的变量只能在主内存中 “诞生”不允许在工作内存中直接使用一个未被初始化load 或 assign的变量换句话说就是对一个变量实施 use 和 store 操作之前,必须先执行过了 assign 和 load 操作。
- 一个变量在同一个时刻只允许一条线程对其进行 lock 操作,但 lock 操作可以被同一条线程重复执行多次,多次执行 lock 后,只有执行相同次数的 unlock 操作,变量才会被解锁。
- 如果对一个变量执行 lock 操作,将会清空工作内存中此变量的值,在执行引擎使用这个变量前,需要重新执行 load 或 assign 操作初始化变量的值。
- 如果一个变量事先没有被 lock 操作锁定,则不允许对它执行 unlock 操作,也不允许去 unlock 一个被其他线程锁定住的变量。
- ......
### Java 内存区域和 JMM 有何区别?
这是一个比较常见的问题,很多初学者非常容易搞混。 **Java 内存区域和内存模型是完全不一样的两个东西**
- JVM 内存结构和 Java 虚拟机的运行时区域相关,定义了 JVM 在运行时如何分区存储程序数据,就比如说堆主要用于存放对象实例。
- Java 内存模型和 Java 的并发编程相关,抽象了线程和主内存之间的关系就比如说线程之间的共享变量必须存储在主内存中,规定了从 Java 源代码到 CPU 可执行指令的这个转化过程要遵守哪些和并发相关的原则和规范,其主要目的是为了简化多线程编程,增强程序可移植性的。
### happens-before 原则是什么?
happens-before 这个概念最早诞生于 Leslie Lamport 于 1978 年发表的论文[《TimeClocks and the Ordering of Events in a Distributed System》](https://lamport.azurewebsites.net/pubs/time-clocks.pdf)。在这篇论文中Leslie Lamport 提出了[逻辑时钟](https://writings.sh/post/logical-clocks)的概念,这也成了第一个逻辑时钟算法 。在分布式环境中,通过一系列规则来定义逻辑时钟的变化,从而能通过逻辑时钟来对分布式系统中的事件的先后顺序进行判断。**逻辑时钟并不度量时间本身,仅区分事件发生的前后顺序,其本质就是定义了一种 happens-before 关系。**
上面提到的 happens-before 这个概念诞生的背景并不是重点,简单了解即可。
JSR 133 引入了 happens-before 这个概念来描述两个操作之间的内存可见性。
**为什么需要 happens-before 原则?** happens-before 原则的诞生是为了程序员和编译器、处理器之间的平衡。程序员追求的是易于理解和编程的强内存模型遵守既定规则编码即可。编译器和处理器追求的是较少约束的弱内存模型让它们尽己所能地去优化性能让性能最大化。happens-before 原则的设计思想其实非常简单:
- 为了对编译器和处理器的约束尽可能少,只要不改变程序的执行结果(单线程程序和正确执行的多线程程序),编译器和处理器怎么进行重排序优化都行。
- 对于会改变程序执行结果的重排序JMM 要求编译器和处理器必须禁止这种重排序。
下面这张是 《Java 并发编程的艺术》这本书中的一张 JMM 设计思想的示意图,非常清晰。
![](https://guide-blog-images.oss-cn-shenzhen.aliyuncs.com/github/javaguide/java/concurrent/image-20220731155332375.png)
了解了 happens-before 原则的设计思想,我们再来看看 JSR-133 对 happens-before 原则的定义:
- 如果一个操作 happens-before 另一个操作,那么第一个操作的执行结果将对第二个操作可见,并且第一个操作的执行顺序排在第二个操作之前。
- 两个操作之间存在 happens-before 关系,并不意味着 Java 平台的具体实现必须要按照 happens-before 关系指定的顺序来执行。如果重排序之后的执行结果,与按 happens-before 关系来执行的结果一致,那么 JMM 也允许这样的重排序。
我们看下面这段代码:
```java
int userNum = getUserNum(); // 1
int teacherNum = getTeacherNum(); // 2
int totalNum = userNum + teacherNum; // 3
```
- 1 happens-before 2
- 2 happens-before 3
- 1 happens-before 3
虽然 1 happens-before 2但对 1 和 2 进行重排序不会影响代码的执行结果,所以 JMM 是允许编译器和处理器执行这种重排序的。但 1 和 2 必须是在 3 执行之前,也就是说 1,2 happens-before 3 。
**happens-before 原则表达的意义其实并不是一个操作发生在另外一个操作的前面,虽然这从程序员的角度上来说也并无大碍。更准确地来说,它更想表达的意义是前一个操作的结果对于后一个操作是可见的,无论这两个操作是否在同一个线程里。**
举个例子:操作 1 happens-before 操作 2即使操作 1 和操作 2 不在同一个线程内JMM 也会保证操作 1 的结果对操作 2 是可见的。
### happens-before 常见规则有哪些?谈谈你的理解?
happens-before 的规则就 8 条,说多不多,重点了解下面列举的 5 条即可。全记是不可能的,很快就忘记了,意义不大,随时查阅即可。
1. **程序顺序规则** :一个线程内,按照代码顺序,书写在前面的操作 happens-before 于书写在后面的操作;
2. **解锁规则** :解锁 happens-before 于加锁;
3. **volatile 变量规则** :对一个 volatile 变量的写操作 happens-before 于后面对这个 volatile 变量的读操作。说白了就是对 volatile 变量的写操作的结果对于发生于其后的任何操作都是可见的。
4. **传递规则** :如果 A happens-before B且 B happens-before C那么 A happens-before C
5. **线程启动规则** Thread 对象的 `start`方法 happens-before 于此线程的每一个动作。
如果两个操作不满足上述任意一个 happens-before 规则那么这两个操作就没有顺序的保障JVM 可以对这两个操作进行重排序。
### happens-before 和 JMM 什么关系?
happens-before 与 JMM 的关系用《Java 并发编程的艺术》这本书中的一张图就可以非常好的解释清楚。
![happens-before 与 JMM 的关系](https://guide-blog-images.oss-cn-shenzhen.aliyuncs.com/github/javaguide/java/concurrent/image-20220731084604667.png)
## 再看并发编程三个重要特性
### 原子性
一次操作或者多次操作,要么所有的操作全部都得到执行并且不会受到任何因素的干扰而中断,要么都不执行。
在 Java 中,可以借助`synchronized` 、各种 `Lock` 以及各种原子类实现原子性。
`synchronized` 和各种 `Lock` 可以保证任一时刻只有一个线程访问该代码块,因此可以保障原子性。各种原子类是利用 CAS (compare and swap) 操作(可能也会用到 `volatile`或者`final`关键字)来保证原子操作。
### 可见性
当一个线程对共享变量进行了修改,那么另外的线程都是立即可以看到修改后的最新值。
在 Java 中,可以借助`synchronized``volatile` 以及各种 `Lock` 实现可见性。
如果我们将变量声明为 `volatile` ,这就指示 JVM这个变量是共享且不稳定的每次使用它都到主存中进行读取。
### 有序性
由于指令重排序问题,代码的执行顺序未必就是编写代码时候的顺序。
我们上面讲重排序的时候也提到过:
> **指令重排序可以保证串行语义一致,但是没有义务保证多线程间的语义也一致** ,所以在多线程下,指令重排序可能会导致一些问题。
在 Java 中,`volatile` 关键字可以禁止指令进行重排序优化。
## 总结
- Java 是最早尝试提供内存模型的语言,其主要目的是为了简化多线程编程,增强程序可移植性的。
- CPU 可以通过制定缓存一致协议(比如 [MESI 协议](https://zh.wikipedia.org/wiki/MESI%E5%8D%8F%E8%AE%AE))来解决内存缓存不一致性问题。
- 为了提升执行速度/性能,计算机在执行程序代码的时候,会对指令进行重排序。 简单来说就是系统在执行代码的时候并不一定是按照你写的代码的顺序依次执行。**指令重排序可以保证串行语义一致,但是没有义务保证多线程间的语义也一致** ,所以在多线程下,指令重排序可能会导致一些问题。
- 你可以把 JMM 看作是 Java 定义的并发编程相关的一组规范,除了抽象了线程和主内存之间的关系之外,其还规定了从 Java 源代码到 CPU 可执行指令的这个转化过程要遵守哪些和并发相关的原则和规范,其主要目的是为了简化多线程编程,增强程序可移植性的。
- JSR 133 引入了 happens-before 这个概念来描述两个操作之间的内存可见性。
## 参考
- 《Java 并发编程的艺术》第三章 Java 内存模型
- 《深入浅出 Java 多线程》http://concurrent.redspider.group/RedSpider.html
- Java 内存访问重排序的研究https://tech.meituan.com/2014/09/23/java-memory-reordering.html
- 嘿,同学,你要的 Java 内存模型 (JMM) 来了https://xie.infoq.cn/article/739920a92d0d27e2053174ef2
- JSR 133 (Java Memory Model) FAQhttps://www.cs.umd.edu/~pugh/java/memoryModel/jsr-133-faq.html

View File

@ -1,5 +1,5 @@
---
title: ThreadLocal 关键字详解
title: ThreadLocal 详解
category: Java
tag:
- Java并发