pull/1/head
595208882@qq.com 3 years ago
parent 64b8ebf37b
commit 7ec0c046a4

@ -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)
**第一阶段(非阻塞):**

@ -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 保存着实际具体值的指针。

Loading…
Cancel
Save