From 4189417d65adaca0e528c71985b1249378d88f48 Mon Sep 17 00:00:00 2001 From: "hailong.sha" Date: Sat, 21 Nov 2020 13:45:01 +0800 Subject: [PATCH 01/14] =?UTF-8?q?Update=20JVM=E5=9E=83=E5=9C=BE=E5=9B=9E?= =?UTF-8?q?=E6=94=B6.md?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../jvm/JVM\345\236\203\345\234\276\345\233\236\346\224\266.md" | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git "a/docs/java/jvm/JVM\345\236\203\345\234\276\345\233\236\346\224\266.md" "b/docs/java/jvm/JVM\345\236\203\345\234\276\345\233\236\346\224\266.md" index e9d219c1a3d..5ed4cd57a26 100644 --- "a/docs/java/jvm/JVM\345\236\203\345\234\276\345\233\236\346\224\266.md" +++ "b/docs/java/jvm/JVM\345\236\203\345\234\276\345\233\236\346\224\266.md" @@ -368,7 +368,7 @@ JDK1.2 以后,Java 对引用的概念进行了扩充,将引用分为强引 ### 4.1 Serial 收集器 -Serial(串行)收集器收集器是最基本、历史最悠久的垃圾收集器了。大家看名字就知道这个收集器是一个单线程收集器了。它的 **“单线程”** 的意义不仅仅意味着它只会使用一条垃圾收集线程去完成垃圾收集工作,更重要的是它在进行垃圾收集工作的时候必须暂停其他所有的工作线程( **"Stop The World"** ),直到它收集结束。 +Serial(串行)收集器是最基本、历史最悠久的垃圾收集器了。大家看名字就知道这个收集器是一个单线程收集器了。它的 **“单线程”** 的意义不仅仅意味着它只会使用一条垃圾收集线程去完成垃圾收集工作,更重要的是它在进行垃圾收集工作的时候必须暂停其他所有的工作线程( **"Stop The World"** ),直到它收集结束。 **新生代采用复制算法,老年代采用标记-整理算法。** ![ Serial 收集器 ](./pictures/jvm垃圾回收/46873026.png) From abec92e6e5f388892fb3c23954714d24e20b1b04 Mon Sep 17 00:00:00 2001 From: "hailong.sha" Date: Sat, 21 Nov 2020 14:17:55 +0800 Subject: [PATCH 02/14] =?UTF-8?q?Update=20=E7=B1=BB=E5=8A=A0=E8=BD=BD?= =?UTF-8?q?=E8=BF=87=E7=A8=8B.md?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- ...61\273\345\212\240\350\275\275\350\277\207\347\250\213.md" | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git "a/docs/java/jvm/\347\261\273\345\212\240\350\275\275\350\277\207\347\250\213.md" "b/docs/java/jvm/\347\261\273\345\212\240\350\275\275\350\277\207\347\250\213.md" index 5a1df706f0e..41ee988043a 100644 --- "a/docs/java/jvm/\347\261\273\345\212\240\350\275\275\350\277\207\347\250\213.md" +++ "b/docs/java/jvm/\347\261\273\345\212\240\350\275\275\350\277\207\347\250\213.md" @@ -35,7 +35,7 @@ Class 文件需要加载到虚拟机中之后才能运行和使用,那么虚 2. 将字节流所代表的静态存储结构转换为方法区的运行时数据结构 3. 在内存中生成一个代表该类的 Class 对象,作为方法区这些数据的访问入口 -虚拟机规范多上面这3点并不具体,因此是非常灵活的。比如:"通过全类名获取定义此类的二进制字节流" 并没有指明具体从哪里获取、怎样获取。比如:比较常见的就是从 ZIP 包中读取(日后出现的JAR、EAR、WAR格式的基础)、其他文件生成(典型应用就是JSP)等等。 +虚拟机规范上面这3点并不具体,因此是非常灵活的。比如:"通过全类名获取定义此类的二进制字节流" 并没有指明具体从哪里获取、怎样获取。比如:比较常见的就是从 ZIP 包中读取(日后出现的JAR、EAR、WAR格式的基础)、其他文件生成(典型应用就是JSP)等等。 **一个非数组类的加载阶段(加载阶段获取类的二进制字节流的动作)是可控性最强的阶段,这一步我们可以去完成还可以自定义类加载器去控制字节流的获取方式(重写一个类加载器的 `loadClass()` 方法)。数组类型不通过类加载器创建,它由 Java 虚拟机直接创建。** @@ -100,7 +100,7 @@ Class 文件需要加载到虚拟机中之后才能运行和使用,那么虚 所以,在JVM生命周期类,由jvm自带的类加载器加载的类是不会被卸载的。但是由我们自定义的类加载器加载的类是可能被卸载的。 -只要想通一点就好了,jdk自带的BootstrapClassLoader,PlatformClassLoader,AppClassLoader负责加载jdk提供的类,所以它们(类加载器的实例)肯定不会被回收。而我们自定义的类加载器的实例是可以被回收的,所以使用我们自定义加载器加载的类是可以被卸载掉的。 +只要想通一点就好了,jdk自带的BootstrapClassLoader,ExtClassLoader,AppClassLoader负责加载jdk提供的类,所以它们(类加载器的实例)肯定不会被回收。而我们自定义的类加载器的实例是可以被回收的,所以使用我们自定义加载器加载的类是可以被卸载掉的。 **参考** From 14f59253eca1a3aaef9472ed58f3b2d41151e43c Mon Sep 17 00:00:00 2001 From: "hailong.sha" Date: Sat, 21 Nov 2020 16:10:06 +0800 Subject: [PATCH 03/14] =?UTF-8?q?Update=20=E7=B1=BB=E6=96=87=E4=BB=B6?= =?UTF-8?q?=E7=BB=93=E6=9E=84.md?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- ...\261\273\346\226\207\344\273\266\347\273\223\346\236\204.md" | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git "a/docs/java/jvm/\347\261\273\346\226\207\344\273\266\347\273\223\346\236\204.md" "b/docs/java/jvm/\347\261\273\346\226\207\344\273\266\347\273\223\346\236\204.md" index c538cfa6042..852b4c4dd8f 100644 --- "a/docs/java/jvm/\347\261\273\346\226\207\344\273\266\347\273\223\346\236\204.md" +++ "b/docs/java/jvm/\347\261\273\346\226\207\344\273\266\347\273\223\346\236\204.md" @@ -183,7 +183,7 @@ public class Employee { method_info methods[methods_count];//一个类可以有个多个方法 ``` -methods_count 表示方法的数量,而 method_info 表示的方法表。 +methods_count 表示方法的数量,而 method_info 表示方法表。 Class 文件存储格式中对方法的描述与对字段的描述几乎采用了完全一致的方式。方法表的结构如同字段表一样,依次包括了访问标志、名称索引、描述符索引、属性表集合几项。 From ae6a6327765fd30817d335e95c970925cb61feb8 Mon Sep 17 00:00:00 2001 From: nightsswatch Date: Mon, 23 Nov 2020 10:41:55 +0800 Subject: [PATCH 04/14] fix typo --- ...ring\344\272\213\345\212\241\346\200\273\347\273\223.md" | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git "a/docs/system-design/framework/spring/Spring\344\272\213\345\212\241\346\200\273\347\273\223.md" "b/docs/system-design/framework/spring/Spring\344\272\213\345\212\241\346\200\273\347\273\223.md" index 1e82a2aae38..0bc45c8d63b 100644 --- "a/docs/system-design/framework/spring/Spring\344\272\213\345\212\241\346\200\273\347\273\223.md" +++ "b/docs/system-design/framework/spring/Spring\344\272\213\345\212\241\346\200\273\347\273\223.md" @@ -51,7 +51,7 @@ public class OrdersService { 另外,数据库事务的 ACID 四大特性是事务的基础,下面简单来了解一下。 -## 2. 事物的特性(ACID)了解么? +## 2. 事务的特性(ACID)了解么? ![](images/spring-transaction/bda7231b-ab05-4e23-95ee-89ac90ac7fcf.png) @@ -143,7 +143,7 @@ Spring 框架中,事务管理相关最重要的 3 个接口如下: - **`TransactionDefinition`**: 事务定义信息(事务隔离级别、传播行为、超时、只读、回滚规则)。 - **`TransactionStatus`**: 事务运行状态。 -我们可以把 **`PlatformTransactionManager`** 接口可以被看作是事务上层的管理者,而 **`TransactionDefinition`** 和 **`TransactionStatus`** 这两个接口可以看作是事物的描述。 +我们可以把 **`PlatformTransactionManager`** 接口可以被看作是事务上层的管理者,而 **`TransactionDefinition`** 和 **`TransactionStatus`** 这两个接口可以看作是事务的描述。 **`PlatformTransactionManager`** 会根据 **`TransactionDefinition`** 的定义比如事务超时时间、隔离级别、传播行为等来进行事务管理 ,而 **`TransactionStatus`** 接口则提供了一些方法来获取事务相应的状态比如是否新事务、是否可以回滚等等。 @@ -238,7 +238,7 @@ public interface TransactionDefinition { ```java public interface TransactionStatus{ - boolean isNewTransaction(); // 是否是新的事物 + boolean isNewTransaction(); // 是否是新的事务 boolean hasSavepoint(); // 是否有恢复点 void setRollbackOnly(); // 设置为只回滚 boolean isRollbackOnly(); // 是否为只回滚 From 29201e640d1ab490afa26e73f33585ba307fd806 Mon Sep 17 00:00:00 2001 From: nightsswatch Date: Mon, 23 Nov 2020 12:07:54 +0800 Subject: [PATCH 05/14] typo fix --- ...\247\201\351\227\256\351\242\230\346\200\273\347\273\223.md" | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git "a/docs/essential-content-for-interview/PreparingForInterview/\347\276\216\345\233\242\351\235\242\350\257\225\345\270\270\350\247\201\351\227\256\351\242\230\346\200\273\347\273\223.md" "b/docs/essential-content-for-interview/PreparingForInterview/\347\276\216\345\233\242\351\235\242\350\257\225\345\270\270\350\247\201\351\227\256\351\242\230\346\200\273\347\273\223.md" index 869416ce018..634cdc4638c 100644 --- "a/docs/essential-content-for-interview/PreparingForInterview/\347\276\216\345\233\242\351\235\242\350\257\225\345\270\270\350\247\201\351\227\256\351\242\230\346\200\273\347\273\223.md" +++ "b/docs/essential-content-for-interview/PreparingForInterview/\347\276\216\345\233\242\351\235\242\350\257\225\345\270\270\350\247\201\351\227\256\351\242\230\346\200\273\347\273\223.md" @@ -106,7 +106,7 @@ request.getRequestDispatcher("login_success.jsp").forward(request, response); ``` -**重定向(Redirect)** 是利用服务器返回的状态码来实现的。客户端浏览器请求服务器的时候,服务器会返回一个状态码。服务器通过 HttpServletRequestResponse 的 setStatus(int status)方法设置状态码。如果服务器返回 301 或者 302,则浏览器会到新的网址重新请求该资源。 +**重定向(Redirect)** 是利用服务器返回的状态码来实现的。客户端浏览器请求服务器的时候,服务器会返回一个状态码。服务器通过 HttpServletResponse 的 setStatus(int status)方法设置状态码。如果服务器返回 301 或者 302,则浏览器会到新的网址重新请求该资源。 1. **从地址栏显示来说**:forward 是服务器请求资源,服务器直接访问目标地址的 URL,把那个 URL 的响应内容读取过来,然后把这些内容再发给浏览器。浏览器根本不知道服务器发送的内容从哪里来的,所以它的地址栏还是原来的地址。redirect 是服务端根据逻辑,发送一个状态码,告诉浏览器重新去请求那个地址。所以地址栏显示的是新的 URL。 2. **从数据共享来说**:forward:转发页面和转发到的页面可以共享 request 里面的数据。redirect:不能共享数据。 From c422061b56770be8b36c8dab0a645b6f8838f28d Mon Sep 17 00:00:00 2001 From: LIU Date: Mon, 23 Nov 2020 22:51:36 +0800 Subject: [PATCH 06/14] fix markdown error --- "docs/java/basis/BIO,NIO,AIO\346\200\273\347\273\223.md" | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git "a/docs/java/basis/BIO,NIO,AIO\346\200\273\347\273\223.md" "b/docs/java/basis/BIO,NIO,AIO\346\200\273\347\273\223.md" index d56074cdd9d..29d2ca86b33 100644 --- "a/docs/java/basis/BIO,NIO,AIO\346\200\273\347\273\223.md" +++ "b/docs/java/basis/BIO,NIO,AIO\346\200\273\347\273\223.md" @@ -15,7 +15,7 @@ - [2)Buffer\(缓冲区\)](#2buffer缓冲区) - [3)Channel \(通道\)](#3channel-通道) - [4)Selectors\(选择器\)](#4selectors选择器) - - [2.3 NIO 读数据和写数据方式](#23-nio-读数据和写数据方式) + - [2.3 NIO 读数据和写数据方式](#23--nio-读数据和写数据方式) - [2.4 NIO核心组件简单介绍](#24-nio核心组件简单介绍) - [2.5 代码示例](#25-代码示例) - [3. AIO \(Asynchronous I/O\)](#3-aio-asynchronous-io) From 09294279b428827d1b26ecf281df41b49b250c05 Mon Sep 17 00:00:00 2001 From: LIU Date: Mon, 23 Nov 2020 22:55:05 +0800 Subject: [PATCH 07/14] fix markdown error --- "docs/java/basis/BIO,NIO,AIO\346\200\273\347\273\223.md" | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git "a/docs/java/basis/BIO,NIO,AIO\346\200\273\347\273\223.md" "b/docs/java/basis/BIO,NIO,AIO\346\200\273\347\273\223.md" index d56074cdd9d..835916e3489 100644 --- "a/docs/java/basis/BIO,NIO,AIO\346\200\273\347\273\223.md" +++ "b/docs/java/basis/BIO,NIO,AIO\346\200\273\347\273\223.md" @@ -11,7 +11,7 @@ - [2. NIO \(New I/O\)](#2-nio-new-io) - [2.1 NIO 简介](#21-nio-简介) - [2.2 NIO的特性/NIO与IO区别](#22-nio的特性nio与io区别) - - [1)Non-blocking IO(非阻塞IO)](#1non-blocking-io(非阻塞io)) + - [1)Non-blocking IO(非阻塞IO)](#1non-blocking-io非阻塞io) - [2)Buffer\(缓冲区\)](#2buffer缓冲区) - [3)Channel \(通道\)](#3channel-通道) - [4)Selectors\(选择器\)](#4selectors选择器) From 89409b0720047862649e9e400ec4f20703242e22 Mon Sep 17 00:00:00 2001 From: LIU Date: Mon, 23 Nov 2020 23:54:47 +0800 Subject: [PATCH 08/14] =?UTF-8?q?=E4=BF=AE=E6=94=B9=E9=94=99=E5=88=AB?= =?UTF-8?q?=E5=AD=97?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- "docs/java/basis/BIO,NIO,AIO\346\200\273\347\273\223.md" | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git "a/docs/java/basis/BIO,NIO,AIO\346\200\273\347\273\223.md" "b/docs/java/basis/BIO,NIO,AIO\346\200\273\347\273\223.md" index d56074cdd9d..b1077076d1e 100644 --- "a/docs/java/basis/BIO,NIO,AIO\346\200\273\347\273\223.md" +++ "b/docs/java/basis/BIO,NIO,AIO\346\200\273\347\273\223.md" @@ -37,7 +37,7 @@ > 当你同步执行某项任务时,你需要等待其完成才能继续执行其他任务。当你异步执行某些操作时,你可以在完成另一个任务之前继续进行。 - **同步** :两个同步任务相互依赖,并且一个任务必须以依赖于另一任务的某种方式执行。 比如在`A->B`事件模型中,你需要先完成 A 才能执行B。 再换句话说,同步调用中被调用者未处理完请求之前,调用不返回,调用者会一直等待结果的返回。 -- **异步**: 两个异步的任务完全独立的,一方的执行不需要等待另外一方的执行。再换句话说,异步调用种一调用就返回结果不需要等待结果返回,当结果返回的时候通过回调函数或者其他方式拿着结果再做相关事情, +- **异步**: 两个异步的任务是完全独立的,一方的执行不需要等待另外一方的执行。再换句话说,异步调用中一调用就返回结果不需要等待结果返回,当结果返回的时候通过回调函数或者其他方式拿着结果再做相关事情, **阻塞和非阻塞** From e924a377ce0947d8f8621d9258d9dd8c2de805b0 Mon Sep 17 00:00:00 2001 From: guide Date: Fri, 27 Nov 2020 15:52:56 +0800 Subject: [PATCH 09/14] Delete some-concepts-of-caching.md --- .../Redis/some-concepts-of-caching.md | 192 ------------------ 1 file changed, 192 deletions(-) delete mode 100644 docs/database/Redis/some-concepts-of-caching.md diff --git a/docs/database/Redis/some-concepts-of-caching.md b/docs/database/Redis/some-concepts-of-caching.md deleted file mode 100644 index 048afb3de33..00000000000 --- a/docs/database/Redis/some-concepts-of-caching.md +++ /dev/null @@ -1,192 +0,0 @@ - - - - -- [1. 缓存的基本思想](#1-缓存的基本思想) -- [2. 使用缓存为系统带来了什么问题](#2-使用缓存为系统带来了什么问题) -- [3. 本地缓存解决方案](#3-本地缓存解决方案) -- [4. 为什么要有分布式缓存?/为什么不直接用本地缓存?](#4-为什么要有分布式缓存为什么不直接用本地缓存) -- [5. 缓存读写模式/更新策略](#5-缓存读写模式更新策略) - - [5.1. Cache Aside Pattern(旁路缓存模式)](#51-cache-aside-pattern旁路缓存模式) - - [5.2. Read/Write Through Pattern(读写穿透)](#52-readwrite-through-pattern读写穿透) - - [5.3. Write Behind Pattern(异步缓存写入)](#53-write-behind-pattern异步缓存写入) - - - - -### 1. 缓存的基本思想 - -很多朋友,只知道缓存可以提高系统性能以及减少请求相应时间,但是,不太清楚缓存的本质思想是什么。 - -缓存的基本思想其实很简单,就是我们非常熟悉的空间换时间。不要把缓存想的太高大上,虽然,它的确对系统的性能提升的性价比非常高。 - -其实,我们在学习使用缓存的时候,你会发现缓存的思想实际在操作系统或者其他地方都被大量用到。 比如 **CPU Cache 缓存的是内存数据用于解决 CPU 处理速度和内存不匹配的问题,内存缓存的是硬盘数据用于解决硬盘访问速度过慢的问题。** **再比如操作系统在 页表方案 基础之上引入了 快表 来加速虚拟地址到物理地址的转换。我们可以把快表理解为一种特殊的高速缓冲存储器(Cache)。** - -回归到业务系统来说:**我们为了避免用户在请求数据的时候获取速度过于缓慢,所以我们在数据库之上增加了缓存这一层来弥补。** - -当别人再问你,缓存的基本思想的时候,就把上面 👆 这段话告诉他,我觉得会让别人对你刮目相看。 - -### 2. 使用缓存为系统带来了什么问题 - -**软件系统设计中没有银弹,往往任何技术的引入都像是把双刃剑。** 你使用的方式得当,就能为系统带来很大的收益。否则,只是费了精力不讨好。 - -简单来说,为系统引入缓存之后往往会带来下面这些问题: - -_ps:其实我觉得引入本地缓存来做一些简单业务场景的话,实际带来的代价几乎可以忽略,下面 👇 主要是针对分布式缓存来说的。_ - -1. **系统复杂性增加** :引入缓存之后,你要维护缓存和数据库的数据一致性、维护热点缓存等等。 -2. **系统开发成本往往会增加** :引入缓存意味着系统需要一个单独的缓存服务,这是需要花费相应的成本的,并且这个成本还是很贵的,毕竟耗费的是宝贵的内存。但是,如果你只是简单的使用一下本地缓存存储一下简单的数据,并且数据量不大的话,那么就不需要单独去弄一个缓存服务。 - -### 3. 本地缓存解决方案 - -_先来聊聊本地缓存,这个实际在很多项目中用的蛮多,特别是单体架构的时候。数据量不大,并且没有分布式要求的话,使用本地缓存还是可以的。_ - -常见的单体架构图如下,我们使用 **Nginx** 来做**负载均衡**,部署两个相同的服务到服务器,两个服务使用同一个数据库,并且使用的是本地缓存。 - -![单体架构](./images/redis-all/单体架构.png) - -_那本地缓存的方案有哪些呢?且听 Guide 给你来说一说。_ - -**一:JDK 自带的 `HashMap` 和 `ConcurrentHashMap` 了。** - -`ConcurrentHashMap` 可以看作是线程安全版本的 `HashMap` ,两者都是存放 key/value 形式的键值对。但是,大部分场景来说不会使用这两者当做缓存,因为只提供了缓存的功能,并没有提供其他诸如过期时间之类的功能。一个稍微完善一点的缓存框架至少要提供:**过期时间**、**淘汰机制**、**命中率统计**这三点。 - -**二: `Ehcache` 、 `Guava Cache` 、 `Spring Cache` 这三者是使用的比较多的本地缓存框架。** - -- `Ehcache` 的话相比于其他两者更加重量。不过,相比于 `Guava Cache` 、 `Spring Cache` 来说, `Ehcache` 支持可以嵌入到 hibernate 和 mybatis 作为多级缓存,并且可以将缓存的数据持久化到本地磁盘中、同时也提供了集群方案(比较鸡肋,可忽略)。 -- `Guava Cache` 和 `Spring Cache` 两者的话比较像。`Guava` 相比于 `Spring Cache` 的话使用的更多一点,它提供了 API 非常方便我们使用,同时也提供了设置缓存有效时间等功能。它的内部实现也比较干净,很多地方都和 `ConcurrentHashMap` 的思想有异曲同工之妙。 -- 使用 `Spring Cache` 的注解实现缓存的话,代码会看着很干净和优雅,但是很容易出现问题比如缓存穿透、内存溢出。 - -**三: 后起之秀 Caffeine。** - -相比于 `Guava` 来说 `Caffeine` 在各个方面比如性能要更加优秀,一般建议使用其来替代 `Guava` 。并且, `Guava` 和 `Caffeine` 的使用方式很像! - -本地缓存固然好,但是缺陷也很明显,比如多个相同服务之间的本地缓存的数据无法共享。 - -### 4. 为什么要有分布式缓存?/为什么不直接用本地缓存? - -本地的缓存的优势非常明显:**低依赖**、**轻量**、**简单**、**成本低**。 - -但是,本地缓存存在下面这些缺陷: - -1. **本地缓存对分布式架构支持不友好**,比如同一个相同的服务部署在多台机器上的时候,各个服务之间的缓存是无法共享的,因为本地缓存只在当前机器上有。 -2. **本地缓存容量受服务部署所在的机器限制明显。** 如果当前系统服务所耗费的内存多,那么本地缓存可用的容量就很少。 - -**我们可以把分布式缓存(Distributed Cache) 看作是一种内存数据库的服务,它的最终作用就是提供缓存数据的服务。** - -如下图所示,就是一个简单的使用分布式缓存的架构图。我们使用 Nginx 来做负载均衡,部署两个相同的服务到服务器,两个服务使用同一个数据库和缓存。 - -![集中式缓存架构](./images/redis-all/集中式缓存架构.png) - - - -使用分布式缓存之后,缓存部署在一台单独的服务器上,即使同一个相同的服务部署在再多机器上,也是使用的同一份缓存。 并且,单独的分布式缓存服务的性能、容量和提供的功能都要更加强大。 - -使用分布式缓存的缺点呢,也很显而易见,那就是你需要为分布式缓存引入额外的服务比如 Redis 或 Memcached,你需要单独保证 Redis 或 Memcached 服务的高可用。 - -### 5. 缓存读写模式/更新策略 - -**下面介绍到的三种模式各有优劣,不存在最佳模式,根据具体的业务场景选择适合自己的缓存读写模式。** - -#### 5.1. Cache Aside Pattern(旁路缓存模式) - -**Cache Aside Pattern 是我们平时使用比较多的一个缓存读写模式,比较适合读请求比较多的场景。** - -Cache Aside Pattern 中服务端需要同时维系 DB 和 cache,并且是以 DB 的结果为准。 - -下面我们来看一下这个策略模式下的缓存读写步骤。 - -**写** : - -- 先更新 DB -- 然后直接删除 cache 。 - -简单画了一张图帮助大家理解写的步骤。 - -![cache-aside-write](images/缓存读写策略/cache-aside-write.png) - -**读** : - -- 从 cache 中读取数据,读取到就直接返回 -- cache中读取不到的话,就从 DB 中读取数据返回 -- 再把数据放到 cache 中。 - -简单画了一张图帮助大家理解读的步骤。 - -![cache-aside-read](images/缓存读写策略/cache-aside-read.png) - - - -你仅仅了解了上面这些内容的话是远远不够的,我们还要搞懂其中的原理。 - -比如说面试官很可能会追问:“**在写数据的过程中,可以先删除 cache ,后更新 DB 么?**” - -**答案:** 那肯定是不行的!因为这样可能会造成**数据库(DB)和缓存(Cache)数据不一致**的问题。为什么呢?比如说请求1 先写数据A,请求2随后读数据A的话就很有可能产生数据不一致性的问题。这个过程可以简单描述为: - -> 请求1先把cache中的A数据删除 -> 请求2从DB中读取数据->请求1再把DB中的A数据更新。 - -当你这样回答之后,面试官可能会紧接着就追问:“**在写数据的过程中,先更新DB,后删除cache就没有问题了么?**” - -**答案:** 理论上来说还是可能会出现数据不一致性的问题,不过概率非常小,因为缓存的写入速度是比数据库的写入速度快很多! - -比如请求1先读数据 A,请求2随后写数据A,并且数据A不在缓存中的话也有可能产生数据不一致性的问题。这个过程可以简单描述为: - -> 请求1从DB读数据A->请求2写更新数据 A 到数据库并把删除cache中的A数据->请求1将数据A写入cache。 - -现在我们再来分析一下 **Cache Aside Pattern 的缺陷**。 - -**缺陷1:首次请求数据一定不在 cache 的问题** - -解决办法:可以将热点数据可以提前放入cache 中。 - -**缺陷2:写操作比较频繁的话导致cache中的数据会被频繁被删除,这样会影响缓存命中率 。** - -解决办法: - -- 数据库和缓存数据强一致场景 :更新DB的时候同样更新cache,不过我们需要加一个锁/分布式锁来保证更新cache的时候不存在线程安全问题。 -- 可以短暂地允许数据库和缓存数据不一致的场景 :更新DB的时候同样更新cache,但是给缓存加一个比较短的过期时间,这样的话就可以保证即使数据不一致的话影响也比较小。 - -#### 5.2. Read/Write Through Pattern(读写穿透) - -Read/Write Through Pattern 中服务端把 cache 视为主要数据存储,从中读取数据并将数据写入其中。cache 服务负责将此数据读取和写入 DB,从而减轻了应用程序的职责。 - -这种缓存读写策略小伙伴们应该也发现了在平时在开发过程中非常少见。抛去性能方面的影响,大概率是因为我们经常使用的分布式缓存 Redis 并没有提供 cache 将数据写入DB的功能。 - -**写(Write Through):** - -- 先查 cache,cache 中不存在,直接更新 DB。 -- cache 中存在,则先更新 cache,然后 cache 服务自己更新 DB(**同步更新 cache 和 DB**)。 - -简单画了一张图帮助大家理解写的步骤。 - -![write-through](images/缓存读写策略/write-through.png) - -**读(Read Through):** - -- 从 cache 中读取数据,读取到就直接返回 。 -- 读取不到的话,先从 DB 加载,写入到 cache 后返回响应。 - -简单画了一张图帮助大家理解读的步骤。 - -![read-through](images/缓存读写策略/read-through.png) - -Read-Through Pattern 实际只是在 Cache-Aside Pattern 之上进行了封装。在 Cache-Aside Pattern 下,发生读请求的时候,如果 cache 中不存在对应的数据,是由客户端自己负责把数据写入 cache,而 Read Through Pattern 则是 cache 服务自己来写入缓存的,这对客户端是透明的。 - -和 Cache Aside Pattern 一样, Read-Through Pattern 也有首次请求数据一定不再 cache 的问题,对于热点数据可以提前放入缓存中。 - -#### 5.3. Write Behind Pattern(异步缓存写入) - -Write Behind Pattern 和 Read/Write Through Pattern 很相似,两者都是由 cache 服务来负责 cache 和 DB 的读写。 - -但是,两个又有很大的不同:**Read/Write Through 是同步更新 cache 和 DB,而 Write Behind Caching 则是只更新缓存,不直接更新 DB,而是改为异步批量的方式来更新 DB。** - -很明显,这种方式对数据一致性带来了更大的挑战,比如cache数据可能还没异步更新DB的话,cache服务可能就就挂掉了。 - -这种策略在我们平时开发过程中也非常非常少见,但是不代表它的应用场景少,比如消息队列中消息的异步写入磁盘、MySQL 的 InnoDB Buffer Pool 机制都用到了这种策略。 - -Write Behind Pattern 下 DB 的写性能非常高,非常适合一些数据经常变化又对数据一致性要求没那么高的场景,比如浏览量、点赞量。 - - - - - From f2c3a98c5fe9753e82fc8e65c472f96045422d3c Mon Sep 17 00:00:00 2001 From: guide Date: Fri, 27 Nov 2020 15:52:59 +0800 Subject: [PATCH 10/14] Update README.md --- README.md | 1 - 1 file changed, 1 deletion(-) diff --git a/README.md b/README.md index 40c4729d2e4..e7f101c7701 100644 --- a/README.md +++ b/README.md @@ -212,7 +212,6 @@ ### Redis -1. [关于缓存的一些重要概念(Redis 前置菜)](docs/database/Redis/some-concepts-of-caching.md) 2. [Redis 常见问题总结](docs/database/Redis/redis-all.md) ## 系统设计 From 5fdb3f861756ede178407f9769c347864378bcb8 Mon Sep 17 00:00:00 2001 From: guide Date: Fri, 27 Nov 2020 16:57:02 +0800 Subject: [PATCH 11/14] =?UTF-8?q?[fix]=E5=9B=BE=E7=89=87=E5=9C=B0=E5=9D=80?= =?UTF-8?q?=E5=87=BA=E9=94=99?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- ...01\351\235\242\350\257\225\351\242\230.md" | 229 +----------------- 1 file changed, 1 insertion(+), 228 deletions(-) diff --git "a/docs/java/collection/Java\351\233\206\345\220\210\346\241\206\346\236\266\345\270\270\350\247\201\351\235\242\350\257\225\351\242\230.md" "b/docs/java/collection/Java\351\233\206\345\220\210\346\241\206\346\236\266\345\270\270\350\247\201\351\235\242\350\257\225\351\242\230.md" index fe58927f55e..0f490ace4b9 100644 --- "a/docs/java/collection/Java\351\233\206\345\220\210\346\241\206\346\236\266\345\270\270\350\247\201\351\235\242\350\257\225\351\242\230.md" +++ "b/docs/java/collection/Java\351\233\206\345\220\210\346\241\206\346\236\266\345\270\270\350\247\201\351\235\242\350\257\225\351\242\230.md" @@ -10,11 +10,6 @@ - [1.1.3.3. Map](#1133-map) - [1.1.4. 如何选用集合?](#114-如何选用集合) - [1.1.5. 为什么要使用集合?](#115-为什么要使用集合) - - [1.1.6. Iterator 迭代器](#116-iterator-迭代器) - - [1.1.6.1. 迭代器 Iterator 是什么?](#1161-迭代器-iterator-是什么) - - [1.1.6.2. 迭代器 Iterator 有啥用?](#1162-迭代器-iterator-有啥用) - - [1.1.6.3. 如何使用?](#1163-如何使用) - - [1.1.7. 有哪些集合是线程不安全的?怎么解决呢?](#117-有哪些集合是线程不安全的怎么解决呢) - [1.2. Collection 子接口之 List](#12-collection-子接口之-list) - [1.2.1. Arraylist 和 Vector 的区别?](#121-arraylist-和-vector-的区别) - [1.2.2. Arraylist 与 LinkedList 区别?](#122-arraylist-与-linkedlist-区别) @@ -46,13 +41,6 @@ - [1.5.1. 排序操作](#151-排序操作) - [1.5.2. 查找,替换操作](#152-查找替换操作) - [1.5.3. 同步控制](#153-同步控制) - - [1.6. 其他重要问题](#16-其他重要问题) - - [1.6.1. 什么是快速失败(fail-fast)?](#161-什么是快速失败fail-fast) - - [1.6.2. 什么是安全失败(fail-safe)呢?](#162-什么是安全失败fail-safe呢) - - [1.6.3. Arrays.asList()避坑指南](#163-arraysaslist避坑指南) - - [1.6.3.1. 简介](#1631-简介) - - [1.6.3.2. 《阿里巴巴 Java 开发手册》对其的描述](#1632-阿里巴巴-java-开发手册对其的描述) - - [1.6.3.3. 使用时的注意事项总结](#1633-使用时的注意事项总结) @@ -112,58 +100,7 @@ 因为我们在实际开发中,存储的数据的类型是多种多样的,于是,就出现了“集合”,集合同样也是用来存储多个数据的。 数组的缺点是一旦声明之后,长度就不可变了;同时,声明数组时的数据类型也决定了该数组存储的数据的类型;而且,数组存储的数据是有序的、可重复的,特点单一。 -但是集合提高了数据存储的灵活性,Java 集合不仅可以用来存储不同类型不同数量的对象,还可以保存具有映射关系的数据 - -### 1.1.6. Iterator 迭代器 - -#### 1.1.6.1. 迭代器 Iterator 是什么? - -```java -public interface Iterator { - //集合中是否还有元素 - boolean hasNext(); - //获得集合中的下一个元素 - E next(); - ...... -} -``` - -`Iterator` 对象称为迭代器(设计模式的一种),迭代器可以对集合进行遍历,但每一个集合内部的数据结构可能是不尽相同的,所以每一个集合存和取都很可能是不一样的,虽然我们可以人为地在每一个类中定义 `hasNext()` 和 `next()` 方法,但这样做会让整个集合体系过于臃肿。于是就有了迭代器。 - -迭代器是将这样的方法抽取出接口,然后在每个类的内部,定义自己迭代方式,这样做就规定了整个集合体系的遍历方式都是 `hasNext()`和`next()`方法,使用者不用管怎么实现的,会用即可。迭代器的定义为:提供一种方法访问一个容器对象中各个元素,而又不需要暴露该对象的内部细节。 - -#### 1.1.6.2. 迭代器 Iterator 有啥用? - -`Iterator` 主要是用来遍历集合用的,它的特点是更加安全,因为它可以确保,在当前遍历的集合元素被更改的时候,就会抛出 `ConcurrentModificationException` 异常。 - -#### 1.1.6.3. 如何使用? - -我们通过使用迭代器来遍历 `HashMap`,演示一下 迭代器 Iterator 的使用。 - -```java - -Map map = new HashMap(); -map.put(1, "Java"); -map.put(2, "C++"); -map.put(3, "PHP"); -Iterator> iterator = map.entrySet().iterator(); -while (iterator.hasNext()) { - Map.Entry entry = iterator.next(); - System.out.println(entry.getKey() + entry.getValue()); -} -``` - -### 1.1.7. 有哪些集合是线程不安全的?怎么解决呢? - -我们常用的 `Arraylist` ,`LinkedList`,`Hashmap`,`HashSet`,`TreeSet`,`TreeMap`,`PriorityQueue` 都不是线程安全的。解决办法很简单,可以使用线程安全的集合来代替。 - -如果你要使用线程安全的集合的话, `java.util.concurrent` 包中提供了很多并发容器供你使用: - -1. `ConcurrentHashMap`: 可以看作是线程安全的 `HashMap` -2. `CopyOnWriteArrayList`:可以看作是线程安全的 `ArrayList`,在读多写少的场合性能非常好,远远好于 `Vector`. -3. `ConcurrentLinkedQueue`:高效的并发队列,使用链表实现。可以看做一个线程安全的 `LinkedList`,这是一个非阻塞队列。 -4. `BlockingQueue`: 这是一个接口,JDK 内部通过链表、数组等方式实现了这个接口。表示阻塞队列,非常适合用于作为数据共享的通道。 -5. `ConcurrentSkipListMap` :跳表的实现。这是一个`Map`,使用跳表的数据结构进行快速查找。 +但是集合提高了数据存储的灵活性,Java 集合不仅可以用来存储不同类型不同数量的对象,还可以保存具有映射关系的数据。 ## 1.2. Collection 子接口之 List @@ -675,170 +612,6 @@ synchronizedMap(Map m) //返回由指定映射支持的同步(线程安 synchronizedSet(Set s) //返回指定 set 支持的同步(线程安全的)set。 ``` -## 1.6. 其他重要问题 - -### 1.6.1. 什么是快速失败(fail-fast)? - -**快速失败(fail-fast)** 是 Java 集合的一种错误检测机制。**在使用迭代器对集合进行遍历的时候,我们在多线程下操作非安全失败(fail-safe)的集合类可能就会触发 fail-fast 机制,导致抛出 `ConcurrentModificationException` 异常。 另外,在单线程下,如果在遍历过程中对集合对象的内容进行了修改的话也会触发 fail-fast 机制。** - -> 注:增强 for 循环也是借助迭代器进行遍历。 - -举个例子:多线程下,如果线程 1 正在对集合进行遍历,此时线程 2 对集合进行修改(增加、删除、修改),或者线程 1 在遍历过程中对集合进行修改,都会导致线程 1 抛出 `ConcurrentModificationException` 异常。 - -**为什么呢?** - -每当迭代器使用 `hashNext()`/`next()`遍历下一个元素之前,都会检测 `modCount` 变量是否为 `expectedModCount` 值,是的话就返回遍历;否则抛出异常,终止遍历。 - -如果我们在集合被遍历期间对其进行修改的话,就会改变 `modCount` 的值,进而导致 `modCount != expectedModCount` ,进而抛出 `ConcurrentModificationException` 异常。 - -> 注:通过 `Iterator` 的方法修改集合的话会修改到 `expectedModCount` 的值,所以不会抛出异常。 - -```java -final void checkForComodification() { - if (modCount != expectedModCount) - throw new ConcurrentModificationException(); -} -``` - -好吧!相信大家已经搞懂了快速失败(fail-fast)机制以及它的原理。 - -我们再来趁热打铁,看一个阿里巴巴手册相关的规定: - -![](images/ad28e3ba-e419-4724-869c-73879e604da1.png) - -有了前面讲的基础,我们应该知道:使用 `Iterator` 提供的 `remove` 方法,可以修改到 `expectedModCount` 的值。所以,才不会再抛出`ConcurrentModificationException` 异常。 - -### 1.6.2. 什么是安全失败(fail-safe)呢? - -明白了快速失败(fail-fast)之后,安全失败(fail-safe)我们就很好理解了。 - -采用安全失败机制的集合容器,在遍历时不是直接在集合内容上访问的,而是先复制原有集合内容,在拷贝的集合上进行遍历。所以,在遍历过程中对原集合所作的修改并不能被迭代器检测到,故不会抛 `ConcurrentModificationException` 异常。 - -### 1.6.3. Arrays.asList()避坑指南 - -最近使用`Arrays.asList()`遇到了一些坑,然后在网上看到这篇文章:[Java Array to List Examples](http://javadevnotes.com/java-array-to-list-examples) 感觉挺不错的,但是还不是特别全面。所以,自己对于这块小知识点进行了简单的总结。 - -#### 1.6.3.1. 简介 - -`Arrays.asList()`在平时开发中还是比较常见的,我们可以使用它将一个数组转换为一个 List 集合。 - -```java -String[] myArray = { "Apple", "Banana", "Orange" }; -List myList = Arrays.asList(myArray); -//上面两个语句等价于下面一条语句 -List myList = Arrays.asList("Apple","Banana", "Orange"); -``` - -JDK 源码对于这个方法的说明: - -```java -/** - *返回由指定数组支持的固定大小的列表。此方法作为基于数组和基于集合的API之间的桥梁,与 Collection.toArray()结合使用。返回的List是可序列化并实现RandomAccess接口。 - */ -public static List asList(T... a) { - return new ArrayList<>(a); -} -``` - -#### 1.6.3.2. 《阿里巴巴 Java 开发手册》对其的描述 - -`Arrays.asList()`将数组转换为集合后,底层其实还是数组,《阿里巴巴 Java 开发手册》对于这个方法有如下描述: - -![阿里巴巴Java开发手-Arrays.asList()方法]() - -#### 1.6.3.3. 使用时的注意事项总结 - -**传递的数组必须是对象数组,而不是基本类型。** - -`Arrays.asList()`是泛型方法,传入的对象必须是对象数组。 - -```java -int[] myArray = { 1, 2, 3 }; -List myList = Arrays.asList(myArray); -System.out.println(myList.size());//1 -System.out.println(myList.get(0));//数组地址值 -System.out.println(myList.get(1));//报错:ArrayIndexOutOfBoundsException -int [] array=(int[]) myList.get(0); -System.out.println(array[0]);//1 -``` - -当传入一个原生数据类型数组时,`Arrays.asList()` 的真正得到的参数就不是数组中的元素,而是数组对象本身!此时 List 的唯一元素就是这个数组,这也就解释了上面的代码。 - -我们使用包装类型数组就可以解决这个问题。 - -```java -Integer[] myArray = { 1, 2, 3 }; -``` - -**使用集合的修改方法:`add()`、`remove()`、`clear()`会抛出异常。** - -```java -List myList = Arrays.asList(1, 2, 3); -myList.add(4);//运行时报错:UnsupportedOperationException -myList.remove(1);//运行时报错:UnsupportedOperationException -myList.clear();//运行时报错:UnsupportedOperationException -``` - -`Arrays.asList()` 方法返回的并不是 `java.util.ArrayList` ,而是 `java.util.Arrays` 的一个内部类,这个内部类并没有实现集合的修改方法或者说并没有重写这些方法。 - -```java -List myList = Arrays.asList(1, 2, 3); -System.out.println(myList.getClass());//class java.util.Arrays$ArrayList -``` - -下图是`java.util.Arrays$ArrayList`的简易源码,我们可以看到这个类重写的方法有哪些。 - -```java - private static class ArrayList extends AbstractList - implements RandomAccess, java.io.Serializable - { - ... - - @Override - public E get(int index) { - ... - } - - @Override - public E set(int index, E element) { - ... - } - - @Override - public int indexOf(Object o) { - ... - } - - @Override - public boolean contains(Object o) { - ... - } - - @Override - public void forEach(Consumer action) { - ... - } - - @Override - public void replaceAll(UnaryOperator operator) { - ... - } - - @Override - public void sort(Comparator c) { - ... - } - } -``` - -我们再看一下`java.util.AbstractList`的`remove()`方法,这样我们就明白为啥会抛出`UnsupportedOperationException`。 - -```java -public E remove(int index) { - throw new UnsupportedOperationException(); -} -``` - **《Java面试突击》:** Java 程序员面试必备的《Java面试突击》V3.0 PDF 版本扫码关注下面的公众号,在后台回复 **"面试突击"** 即可免费领取! From 07b18f80b0c6611e71b7db022336518c58173a21 Mon Sep 17 00:00:00 2001 From: guide Date: Fri, 27 Nov 2020 17:24:49 +0800 Subject: [PATCH 12/14] =?UTF-8?q?Update=202020=E6=9C=80=E6=96=B0Java?= =?UTF-8?q?=E5=B9=B6=E5=8F=91=E8=BF=9B=E9=98=B6=E5=B8=B8=E8=A7=81=E9=9D=A2?= =?UTF-8?q?=E8=AF=95=E9=A2=98=E6=80=BB=E7=BB=93.md?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- ...350\257\225\351\242\230\346\200\273\347\273\223.md" | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git "a/docs/java/multi-thread/2020\346\234\200\346\226\260Java\345\271\266\345\217\221\350\277\233\351\230\266\345\270\270\350\247\201\351\235\242\350\257\225\351\242\230\346\200\273\347\273\223.md" "b/docs/java/multi-thread/2020\346\234\200\346\226\260Java\345\271\266\345\217\221\350\277\233\351\230\266\345\270\270\350\247\201\351\235\242\350\257\225\351\242\230\346\200\273\347\273\223.md" index e55612460b6..8ac04a67352 100644 --- "a/docs/java/multi-thread/2020\346\234\200\346\226\260Java\345\271\266\345\217\221\350\277\233\351\230\266\345\270\270\350\247\201\351\235\242\350\257\225\351\242\230\346\200\273\347\273\223.md" +++ "b/docs/java/multi-thread/2020\346\234\200\346\226\260Java\345\271\266\345\217\221\350\277\233\351\230\266\345\270\270\350\247\201\351\235\242\350\257\225\351\242\230\346\200\273\347\273\223.md" @@ -968,12 +968,12 @@ protected final boolean compareAndSetState(int expect, int update) { **AQS 定义两种资源共享方式** -- **Exclusive**(独占):只有一个线程能执行,如 ReentrantLock。又可分为公平锁和非公平锁: +- **Exclusive**(独占):只有一个线程能执行,如 `ReentrantLock`。又可分为公平锁和非公平锁: - 公平锁:按照线程在队列中的排队顺序,先到者先拿到锁 - 非公平锁:当线程要获取锁时,无视队列顺序直接去抢锁,谁抢到就是谁的 -- **Share**(共享):多个线程可同时执行,如 Semaphore/CountDownLatch。Semaphore、CountDownLatch、 CyclicBarrier、ReadWriteLock 我们都会在后面讲到。 +- **Share**(共享):多个线程可同时执行,如` CountDownLatch`、`Semaphore`、`CountDownLatch`、 `CyclicBarrier`、`ReadWriteLock` 我们都会在后面讲到。 -ReentrantReadWriteLock 可以看成是组合式,因为 ReentrantReadWriteLock 也就是读写锁允许多个线程同时对某一资源进行读。 +`ReentrantReadWriteLock` 可以看成是组合式,因为 `ReentrantReadWriteLock` 也就是读写锁允许多个线程同时对某一资源进行读。 不同的自定义同步器争用共享资源的方式也不同。自定义同步器在实现时只需要实现共享资源 state 的获取与释放方式即可,至于具体线程等待队列的维护(如获取资源失败入队/唤醒出队等),AQS 已经在顶层实现好了。 @@ -981,7 +981,7 @@ ReentrantReadWriteLock 可以看成是组合式,因为 ReentrantReadWriteLock 同步器的设计是基于模板方法模式的,如果需要自定义同步器一般的方式是这样(模板方法模式很经典的一个应用): -1. 使用者继承 AbstractQueuedSynchronizer 并重写指定的方法。(这些重写方法很简单,无非是对于共享资源 state 的获取和释放) +1. 使用者继承 `AbstractQueuedSynchronizer` 并重写指定的方法。(这些重写方法很简单,无非是对于共享资源 state 的获取和释放) 2. 将 AQS 组合在自定义同步组件的实现中,并调用其模板方法,而这些模板方法会调用使用者重写的方法。 这和我们以往通过实现接口的方式有很大区别,这是模板方法模式很经典的一个运用。 @@ -1001,7 +1001,7 @@ tryReleaseShared(int)//共享方式。尝试释放资源,成功则返回true 以 ReentrantLock 为例,state 初始化为 0,表示未锁定状态。A 线程 lock()时,会调用 tryAcquire()独占该锁并将 state+1。此后,其他线程再 tryAcquire()时就会失败,直到 A 线程 unlock()到 state=0(即释放锁)为止,其它线程才有机会获取该锁。当然,释放锁之前,A 线程自己是可以重复获取此锁的(state 会累加),这就是可重入的概念。但要注意,获取多少次就要释放多么次,这样才能保证 state 是能回到零态的。 -再以 CountDownLatch 以例,任务分为 N 个子线程去执行,state 也初始化为 N(注意 N 要与线程个数一致)。这 N 个子线程是并行执行的,每个子线程执行完后 countDown()一次,state 会 CAS(Compare and Swap)减 1。等到所有子线程都执行完后(即 state=0),会 unpark()主调用线程,然后主调用线程就会从 await()函数返回,继续后余动作。 +再以 `CountDownLatch` 以例,任务分为 N 个子线程去执行,state 也初始化为 N(注意 N 要与线程个数一致)。这 N 个子线程是并行执行的,每个子线程执行完后` countDown()` 一次,state 会 CAS(Compare and Swap)减 1。等到所有子线程都执行完后(即 state=0),会 unpark()主调用线程,然后主调用线程就会从 `await()` 函数返回,继续后余动作。 一般来说,自定义同步器要么是独占方法,要么是共享方式,他们也只需实现`tryAcquire-tryRelease`、`tryAcquireShared-tryReleaseShared`中的一种即可。但 AQS 也支持自定义同步器同时实现独占和共享两种方式,如`ReentrantReadWriteLock`。 From a4f500c1d46193808aecd7570824e0dc64273138 Mon Sep 17 00:00:00 2001 From: guide Date: Fri, 27 Nov 2020 17:24:51 +0800 Subject: [PATCH 13/14] =?UTF-8?q?Update=20Java=E5=9F=BA=E7=A1=80=E7=9F=A5?= =?UTF-8?q?=E8=AF=86.md?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- ...72\347\241\200\347\237\245\350\257\206.md" | 24 ------------------- 1 file changed, 24 deletions(-) diff --git "a/docs/java/basis/Java\345\237\272\347\241\200\347\237\245\350\257\206.md" "b/docs/java/basis/Java\345\237\272\347\241\200\347\237\245\350\257\206.md" index ca79e0449b3..da60ba36cd7 100644 --- "a/docs/java/basis/Java\345\237\272\347\241\200\347\237\245\350\257\206.md" +++ "b/docs/java/basis/Java\345\237\272\347\241\200\347\237\245\350\257\206.md" @@ -53,8 +53,6 @@ - [2.3. 修饰符](#23-修饰符) - [2.3.1. 在一个静态方法内调用一个非静态成员为什么是非法的?](#231-在一个静态方法内调用一个非静态成员为什么是非法的) - [2.3.2. 静态方法和实例方法有何不同](#232-静态方法和实例方法有何不同) - - [2.4. 接口和抽象类](#24-接口和抽象类) - - [2.4.1. 接口和抽象类的区别是什么?](#241-接口和抽象类的区别是什么) - [2.5. 其它重要知识点](#25-其它重要知识点) - [2.5.1. String StringBuffer 和 StringBuilder 的区别是什么? String 为什么是不可变的?](#251-string-stringbuffer-和-stringbuilder-的区别是什么-string-为什么是不可变的) - [2.5.2. Object 类的常见方法总结](#252-object-类的常见方法总结) @@ -798,7 +796,6 @@ Java 程序设计语言对对象采用的不是引用调用,实际上,对象 | 发生阶段 | 编译期 | 运行期 | - **方法的重写要遵循“两同两小一大”**(以下内容摘录自《疯狂 Java 讲义》,[issue#892](https://github.com/Snailclimb/JavaGuide/issues/892) ): - “两同”即方法名相同、形参列表相同; @@ -986,27 +983,6 @@ public class Student { 2. 静态方法在访问本类的成员时,只允许访问静态成员(即静态成员变量和静态方法),而不允许访问实例成员变量和实例方法;实例方法则无此限制。 -### 2.4. 接口和抽象类 - -#### 2.4.1. 接口和抽象类的区别是什么? - -1. 接口的方法默认是 public,所有方法在接口中不能有实现(Java 8 开始接口方法可以有默认实现),而抽象类可以有非抽象的方法。 -2. 接口中除了 static、final 变量,不能有其他变量,而抽象类中则不一定。 -3. 一个类可以实现多个接口,但只能实现一个抽象类。接口自己本身可以通过 extends 关键字扩展多个接口。 -4. 接口方法默认修饰符是 public,抽象方法可以有 public、protected 和 default 这些修饰符(抽象方法就是为了被重写所以不能使用 private 关键字修饰!)。 -5. 从设计层面来说,抽象是对类的抽象,是一种模板设计,而接口是对行为的抽象,是一种行为的规范。 - -> 备注: -> -> 1. 在 JDK8 中,接口也可以定义静态方法,可以直接用接口名调用。实现类和实现是不可以调用的。如果同时实现两个接口,接口中定义了一样的默认方法,则必须重写,不然会报错。(详见 issue:[https://github.com/Snailclimb/JavaGuide/issues/146](https://github.com/Snailclimb/JavaGuide/issues/146)。 -> 2. jdk9 的接口被允许定义私有方法 。 - -总结一下 jdk7~jdk9 Java 中接口概念的变化([相关阅读](https://www.geeksforgeeks.org/private-methods-java-9-interfaces/)): - -1. 在 jdk 7 或更早版本中,接口里面只能有常量变量和抽象方法。这些接口方法必须由选择实现接口的类实现。 -2. jdk8 的时候接口可以有默认方法和静态方法功能。 -3. Jdk 9 在接口中引入了私有方法和私有静态方法。 - ### 2.5. 其它重要知识点 #### 2.5.1. String StringBuffer 和 StringBuilder 的区别是什么? String 为什么是不可变的? From 1b85c6990acdd242f105d9c98212dc525d562b14 Mon Sep 17 00:00:00 2001 From: guide Date: Sat, 28 Nov 2020 21:33:22 +0800 Subject: [PATCH 14/14] =?UTF-8?q?Update=20BIO,NIO,AIO=E6=80=BB=E7=BB=93.md?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- "docs/java/basis/BIO,NIO,AIO\346\200\273\347\273\223.md" | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git "a/docs/java/basis/BIO,NIO,AIO\346\200\273\347\273\223.md" "b/docs/java/basis/BIO,NIO,AIO\346\200\273\347\273\223.md" index 61bf7da4db6..cf7b4fb0679 100644 --- "a/docs/java/basis/BIO,NIO,AIO\346\200\273\347\273\223.md" +++ "b/docs/java/basis/BIO,NIO,AIO\346\200\273\347\273\223.md" @@ -14,8 +14,8 @@ - [1)Non-blocking IO(非阻塞IO)](#1non-blocking-io非阻塞io) - [2)Buffer\(缓冲区\)](#2buffer缓冲区) - [3)Channel \(通道\)](#3channel-通道) - - [4)Selectors\(选择器\)](#4selectors选择器) - - [2.3 NIO 读数据和写数据方式](#23--nio-读数据和写数据方式) + - [4)Selectors\(选择器\)](#4selector-选择器) + - [2.3 NIO 读数据和写数据方式](#23-nio-读数据和写数据方式) - [2.4 NIO核心组件简单介绍](#24-nio核心组件简单介绍) - [2.5 代码示例](#25-代码示例) - [3. AIO \(Asynchronous I/O\)](#3-aio-asynchronous-io)