From 7ec0c046a455445460bdb9d931400702cf0baa4b Mon Sep 17 00:00:00 2001 From: "595208882@qq.com" Date: Sat, 7 Aug 2021 08:46:40 +0800 Subject: [PATCH] adjust --- JAVA.md | 36 ++++++++++++++++++++++++++++++++++++ Middleware.md | 46 ++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 82 insertions(+) diff --git a/JAVA.md b/JAVA.md index 32b0bb6..6aec3b1 100644 --- a/JAVA.md +++ b/JAVA.md @@ -1764,6 +1764,41 @@ Java8 对 ConcurrentHashMap 进行了比较大的改动,Java8 也引入了红黑 +**ConcurrentHashMap在1.7和1.8的区别** + +- **整体结构** + - 1.7:`Segment + HashEntry + Unsafe` + - 1.8:移除Segment,使锁的粒度更小,`Synchronized + CAS + Node + Unsafe` + +- **put()** + + - 1.7:先定位Segment,再定位桶,put全程加锁,没有获取锁的线程提前找桶的位置,并最多自旋64次获取锁,超过则挂起 + 1. 定位Segment:先通过key的 `rehash值的高位` 和 `segments数组大小-1` 相与得到在 segments中的位置 + 2. 定位桶:然后在通过 `key的rehash值` 和 `table数组大小-1` 相与得到在table中的位置 + + - 1.8:由于移除了Segment,可根据 `rehash值` 直接定位到桶,拿到table[i]的 `首节点first`后进行判断: + 1. 如果为 `null` ,通过 `CAS` 的方式把 value put进去 + 2. 如果 `非null` ,并且 `first.hash == -1` ,说明其他线程在扩容,参与一起扩容 + 3. 如果 `非null` ,并且 `first.hash != -1` ,Synchronized锁住 first节点,判断是链表还是红黑树,遍历插入。 + +- **get()** + - 1.7和1.8基本类似,由于value声明为volatile,保证了修改的可见性,因此不需要加锁 + +- **resize()** + - 1.7:与HashMap的 resize() 没太大区别,都是在 put() 元素时去做的扩容,所以在1.7中的实现是获得了锁之后,在单线程中去做扩容(1.`new个2倍数组` 2.`遍历old数组节点搬去新数组`),避免了HashMap在1.7中扩容时死循环的问题,保证线程安全 + - 1.8:支持并发扩容,HashMap扩容在1.8中由头插改为尾插(为了避免死循环问题),ConcurrentHashmap也是,迁移也是从尾部开始,扩容前在桶的头部放置一个hash值为-1的节点,这样别的线程访问时就能判断是否该桶已经被其他线程处理过了 + +- **size()** + - 1.7:很经典的思路 + 1. 先采用不加锁的方式,计算两次,如果两次结果一样,说明是正确的,返回 + 2. 如果两次结果不一样,则把所有 segment 锁住,重新计算所有 segment的 `Count` 的和 + - 1.8:由于没有segment的概念,所以只需要用一个 `baseCount` 变量来记录ConcurrentHashMap 当前 `节点的个数`。 + 1. 先尝试通过CAS 修改 `baseCount` + 2. 如果多线程竞争激烈,某些线程CAS失败,那就CAS尝试将 `CELLSBUSY` 置1,成功则可以把 `baseCount变化的次数` 暂存到一个数组 `counterCells` 里,后续数组 `counterCells` 的值会加到 `baseCount` 中 + 3. 如果 `CELLSBUSY` 置1失败又会反复进行CAS`baseCount` 和 CAS`counterCells`数组 + + + ### ConcurrentSkipListMap ConcurrentSkipListMap是线程安全的有序的哈希表(相当于线程安全的TreeMap)。它继承于AbstractMap类,并且实现ConcurrentNavigableMap接口。ConcurrentSkipListMap是通过“跳表”来实现的,它支持并发。 @@ -3854,6 +3889,7 @@ AIO(异步非阻塞IO,即NIO.2)。异步IO模型中,用户线程直接使用 信号驱动式I/O是指进程预先告知内核,使得某个文件描述符上发生了变化时,内核使用信号通知该进程。在信号驱动式I/O模型,进程使用socket进行信号驱动I/O,并建立一个SIGIO信号处理函数,当进程通过该信号处理函数向内核发起I/O调用时,内核并没有准备好数据报,而是返回一个信号给进程,此时进程可以继续发起其他I/O调用。也就是说,在第一阶段内核准备数据的过程中,进程并不会被阻塞,会继续执行。当数据报准备好之后,内核会递交SIGIO信号,通知用户空间的信号处理程序,数据已准备好;此时进程会发起recvfrom的系统调用,这一个阶段与阻塞式I/O无异。也就是说,在第二阶段内核复制数据到用户空间的过程中,进程同样是被阻塞的。 **信号驱动式I/O的整个过程图如下:** + ![信号驱动式IO](images/JAVA/信号驱动式IO.png) **第一阶段(非阻塞):** diff --git a/Middleware.md b/Middleware.md index d1477ec..314e5f9 100644 --- a/Middleware.md +++ b/Middleware.md @@ -105,10 +105,56 @@ org.springframework.boot.env.YamlPropertySourceLoader - **Set:** 去重、赞、踩、共同好友等 - **Zset:** 访问量排行榜、点击量排行榜等 +### String(字符串) + +String 数据结构是简单的 key-value 类型,value 不仅可以是 String,也可以是数字(当数字类型用 Long 可以表示的时候encoding 就是整型,其他都存储在 sdshdr 当做字符串)。 + + + +### Hash(字典) + +在 Memcached 中,我们经常将一些结构化的信息打包成 hashmap,在客户端序列化后存储为一个字符串的值(一般是 JSON 格式),比如用户的昵称、年龄、性别、积分等。这时候在需要修改其中某一项时,通常需要将字符串(JSON)取出来,然后进行反序列化,修改某一项的值,再序列化成字符串(JSON)存储回去。简单修改一个属性就干这么多事情,消耗必定是很大的,也不适用于一些可能并发操作的场合(比如两个并发的操作都需要修改积分)。而 Redis 的 Hash 结构可以使你像在数据库中 Update 一个属性一样只修改某一项属性值。 + +- 存储、读取、修改用户属性 + + + +### List(列表) + +List 说白了就是链表(redis 使用双端链表实现的 List),相信学过数据结构知识的人都应该能理解其结构。使用 List 结构,我们可以轻松地实现最新消息排行等功能(比如新浪微博的 TimeLine )。List 的另一个应用就是消息队列,可以利用 List 的 *PUSH 操作,将任务存在 List 中,然后工作线程再用 POP 操作将任务取出进行执行。Redis 还提供了操作 List 中某一段元素的 API,你可以直接查询,删除 List 中某一段的元素。 + +- 微博 TimeLine +- 消息队列 + + + +### Set(集合) + +Set 就是一个集合,集合的概念就是一堆不重复值的组合。利用 Redis 提供的 Set 数据结构,可以存储一些集合性的数据。比如在微博应用中,可以将一个用户所有的关注人存在一个集合中,将其所有粉丝存在一个集合。因为 Redis 非常人性化的为集合提供了求交集、并集、差集等操作,那么就可以非常方便的实现如共同关注、共同喜好、二度好友等功能,对上面的所有集合操作,你还可以使用不同的命令选择将结果返回给客户端还是存集到一个新的集合中。 + +- 共同好友、二度好友 +- 利用唯一性,可以统计访问网站的所有独立 IP +- 好友推荐的时候,根据 tag 求交集,大于某个 threshold 就可以推荐 + + + +### Sorted Set(有序集合) + +和Sets相比,Sorted Sets是将 Set 中的元素增加了一个权重参数 score,使得集合中的元素能够按 score 进行有序排列,比如一个存储全班同学成绩的 Sorted Sets,其集合 value 可以是同学的学号,而 score 就可以是其考试得分,这样在数据插入集合的时候,就已经进行了天然的排序。另外还可以用 Sorted Sets 来做带权重的队列,比如普通消息的 score 为1,重要消息的 score 为2,然后工作线程可以选择按 score 的倒序来获取工作任务。让重要的任务优先执行。 + +- 带有权重的元素,比如一个游戏的用户得分排行榜 +- 比较复杂的数据结构,一般用到的场景不算太多 + + + +## 数据结构 + 当然是为了追求速度,不同数据类型使用不同的数据结构速度才得以提升。每种数据类型都有一种或者多种数据结构来支撑,底层数据结构有 6 种。 ![Redis数据类型与底层数据结构关系](images/Middleware/Redis数据类型与底层数据结构关系.png) + + ### Redis hash字典 Redis 整体就是一个 哈希表来保存所有的键值对,无论数据类型是 5 种的任意一种。哈希表,本质就是一个数组,每个元素被叫做哈希桶,不管什么数据类型,每个桶里面的 entry 保存着实际具体值的指针。