ReentrantLock源码解析
本篇博客中的大部分内容并非原创,仅做整理作为学习资料使用
参考:https://www.cnblogs.com/tong-yuan/p/ReentrantLock.html
简介 ReentrantLock 是 AQS 独占模式的典型示例,接下来我们分析 ReentrantLock 是如何实现的。
根据类图,我们可以发现,ReentrantLock实现了Lock接口,Lock接口里面定义了java中锁应该实现的几个方法:
123456789101112// 获取锁void lock();// 获取锁(可中断)void lockInterruptibly() throws InterruptedException;// 尝试获取锁,如果没获取到锁,就返回falseboolean tryLock();// 尝试获取锁,如果没获取到锁,就等待一段时间,这段时间内还没获取到锁就返回falseboolean tryLock(long time, TimeUnit unit) throws InterruptedException;// 释放锁void unlock();// 条件锁Cond ...
AQS是什么?
因为 AQS 是抽象类,无法实例化,笔者建议读者结合 ReentrantLock 去阅读和调试 AQS 的源码。
简介AQS (Abstract Queued Synchronizer) 是 JDK 提供的一套基于 FIFO 同步队列的阻塞锁和相关同步器的一个同步框架,通过 AQS 我们可以很容易地实现我们自己需要的独占锁或共享锁。 在 Java 中,信号量、ReentrantLock、CountDownLatch 等工具都是通过 AQS 来实现的。
AQS中维护了两个队列,同步队列和阻塞队列(ConditionObject)。同步队列用于锁的实现,是基于链表实现的双向队列,也是CLH锁的变种。阻塞队列也是基于链表实现,但是单向的,和wait、notify具有同样功效,可以用于生产者消费者模式的实现,如JUC中部分阻塞队列的实现。
整体介绍实现一把锁的核心要素一把锁最基本的功能要有阻塞和唤醒,通常情况下还要设计成可重入的,否则一个线程获取锁进入互斥状态后,再次获取锁将会被阻塞,产生死锁问题。如下是设计一把锁,需要的几个核心要素:
需要一个 state 变量,记录锁的状态。stat ...
CLH锁
在进行 Java 并发学习的时候发现关于锁这一涵盖性术语有太多的子概念,所以本篇博文首先对锁相关的概念进行一个简单的总结,然后讲解 AQS 的基石— CLH 锁。
术语表
独占锁: 确保同一时间只有一个线程可以访问共享资源,比如synchronized关键字和ReentrantLock类。
共享锁: 允许多个线程同时访问共享资源,以提高系统的并发性能。读写锁通常是一种共享锁,其中读锁是共享的,而写锁是独占的。
自旋锁: 在等待共享资源时,线程占用CPU来不停地检查共享资源是否可用,直到共享资源空闲为止。这是声称为忙等待,因为线程不会释放CPU。
互斥锁: 让不同的线程单独地访问共享资源。
读写锁: 允许多个线程同时读共享资源,但在写共享资源时只有一个线程可以执行。
悲观锁: 整个数据处理过程中,将数据处于锁定状态,只允许一个线程进行操作,其他线程需要等待。它基于独占锁机制,适用于对数据竞争的频率较高的情况,比如对大多数写操作的情况。使用悲观锁需要频繁的加锁和解锁,因此可能导致线程间的竞争和性能瓶颈。
乐观锁: 在整个数据处理过程中,不使用锁机制,甚至不考虑其他线程的存在,每个线程都可以 ...
Elasticsearch学习笔记(二)
在上一篇文章中,我们已经导入了大量数据到 elasticsearch 中,实现了数据的存储。不过查询数据时依然采用的是根据 id 查询,而非模糊搜索。
所以在这篇文章中,我们来研究下 elasticsearch 的数据搜索功能。Elasticsearch 提供了基于 JSON 的 DSL(Domain Specific Language)语句来定义查询条件,其 JavaAPI 就是在组织 DSL 条件。
因此,我们先学习 DSL 的查询语法,然后再基于 DSL 来对照学习 JavaAPI。
1.DSL 查询Elasticsearch 的查询可以分为两大类:
叶子查询(Leaf query clauses):一般是在特定的字段里查询特定值,属于简单查询,很少单独使用。
复合查询(Compound query clauses):以逻辑方式组合多个叶子查询或者更改叶子查询的行为方式。
1.1.快速入门我们依然在 Kibana 的 DevTools 中学习查询的 DSL 语法。首先来看查询的语法结构:
12345678GET /{索引库名}/_search{ ...
Elasticsearch学习笔记(一)
在项目中基础的搜索功能是基于数据库的模糊搜索来实现的,存在很多问题。
首先,查询效率较低。由于数据库模糊查询不走索引,在数据量较大的时候,查询性能很差。数据库模糊查询随着表数据量的增多,查询性能的下降会非常明显,而搜索引擎的性能则不会随着数据增多而下降太多。
其次,功能单一。数据库的模糊搜索功能单一,匹配条件非常苛刻,必须恰好包含用户搜索的关键字。而在搜索引擎中,用户输入出现个别错字,或者用拼音搜索、同义词搜索都能正确匹配到数据。
综上,在面临海量数据的搜索,或者有一些复杂搜索需求的时候,推荐使用专门的搜索引擎来实现搜索功能。
目前全球的搜索引擎技术排名如下:
排名第一的就是我们今天要学习的 Elasticsearch,Elasticsearch 的官网是这样介绍自己的:
Elasticsearch 是一个分布式、RESTful 风格的搜索和数据分析引擎。
Elasticsearch 是一款非常强大的开源搜索引擎,支持的功能非常多,例如:
代码搜索
商品搜索
解决方案搜索
地图搜索
本文接下来会讲解下面的内容:
倒排索引原理
IK 分词器
索引库 Mapping 映射的属性 ...
RabbitMQ学习笔记(一)
微服务一旦拆分,必然涉及到服务之间的相互调用,目前我们服务之间调用采用的都是基于 OpenFeign 的调用。这种调用中,调用者发起请求后需要等待服务提供者执行业务返回结果后,才能继续执行后面的业务。也就是说调用者在调用过程中处于阻塞状态,因此我们称这种调用方式为同步调用,也可以叫同步通讯。但在很多场景下,我们可能需要采用异步通讯的方式,为什么呢?
我们先来看看什么是同步通讯和异步通讯。如图:
举例:
同步通讯:就如同打视频电话,双方的交互都是实时的。因此同一时刻你只能跟一个人打视频电话。
异步通讯:就如同发微信聊天,双方的交互不是实时的,你不需要立刻给对方回应。因此你可以多线操作,同时跟多人聊天。
两种方式各有优劣,打电话可以立即得到响应,但是你却不能跟多个人同时通话。发微信可以同时与多个人收发微信,但是往往响应会有延迟。
所以,如果我们的业务需要实时得到服务提供方的响应,则应该选择同步通讯(同步调用)。而如果我们追求更高的效率,并且不需要实时响应,则应该选择异步通讯(异步调用)。
同步调用的方式我们已经学过了,之前的 OpenFeign 调用就是。但是:
异步调用又该如何实 ...
RabbitMQ学习笔记(二)
假如MQ 通知失败,业务可能就会出现不一致的问题。因此,我们必须尽可能确保 MQ 消息的可靠性,即:消息应该至少被消费者处理一次。
那么问题来了:
我们该如何确保MQ消息的可靠性?
如果真的发送失败,有没有其它的兜底方案?
接下来我们就一点点来分析。
0.消息丢失的可能性首先,我们一起分析一下消息丢失的可能性有哪些。
消息从发送者发送消息,到消费者处理消息,需要经过的流程是这样的:
消息从生产者到消费者的每一步都可能导致消息丢失:
发送消息时丢失:
生产者发送消息时连接 MQ 失败
生产者发送消息到达 MQ 后未找到 Exchange
生产者发送消息到达 MQ 的 Exchange 后,未找到合适的 Queue
消息到达 MQ 后,处理消息的进程发生异常
MQ 导致消息丢失:
消息到达 MQ,保存到队列后,尚未消费就突然宕机
消费者处理消息时:
消息接收后尚未处理突然宕机
消息接收后处理过程中抛出异常
综上,我们要解决消息丢失问题,保证 MQ 的可靠性,就必须从 3 个方面入手:
确保生产者一定把消息发送到 MQ
确保 MQ 不会将消息弄丢
确保消费者一 ...
Redis实现计数统计
场景分析在社区项目中,我们有很多计数相关的需求,比如
文章相关的:
点赞数
阅读数
收藏数
评论数
用户相关的:
用户发表的文章数
用户文章的阅读总数
用户文章被收藏数
用户文章被点赞数
用户粉丝数
用户关注数
首先看一下我们的场景:
文章阅读数是个单独的表,每次从数据库查询文章详情的时候,DB中阅读量就会+1,然后返回。
除此之外,其他计数并不会直接存储在DB中,比如对于点赞数,我们在DB中存储的是什么呢?是点赞这个记录,而点赞数通过查询DB计算出来的,我们通过在SQL中使用sum函数计算出来。
我们再来看,在业务中,什么场景是高频的,什么场景是低频的。
查询操作是高频的:对上述计数的查询是高频的,每次刷新页面,打开任何一个页面都需要查询。
更新操作是低频的:很多人只是浏览查看,参与社区活动(点赞,评论,收藏)还是比较少的。
那么针对这种场景,我们的做法是这样的,
对这些计数的查询是比较高频的业务,如果每次都要查询数据库(使用SQL语句计算出来),是比较耗时的,因为可以基于Redis的incr指令实现一个计数器。
更新计数的话,我们的方案会把记录写DB。因为更新计 ...
实现用户活跃度排行榜
需求分析用户活跃度榜单是一个能够鼓励用户参与社区活动的有效手段。
用户活跃度计算方式:
用户每访问一个新的页面+1分
对于一篇文章,点赞、收藏+2分;取消点赞、取消收藏,将之前的活跃分收回
文章评论+3分,删除评论,将之前的活跃分收回
关注用户 +2分,取消关注,将之前的活跃分收回
发布—篇审核通过的文章+10分,删除文章,将之前的活跃分收回
榜单(分为月榜和日榜):
展示活跃度最高的前三十名用户
方案设计首先是存储单元,针对一个排行榜,思考排行榜上面每一位需要存储哪些信息:
123456//用来表明具体的用户long userId;//用户在排行榜上的排名long rank;//用户的积分long score;
我们可以使用Redis中的 zset 数据结构来实现,zset维护了一个带权重的有序集合:
set:集合确保里面的每个元素只出现一次
权重:就是我们的score
zset:根据score进行排序的集合
从zset的特性来看,我们每个用户的积分,丢到zset中,就是一个带权重的元素,而且是已经排好序的了,只需要获取元素对应的index,就是我们预期的排名,非常方便 ...
Redis benchmark使用教程
Redis 包括 redis-benchmark 实用程序,它模拟同时发送 M 个总查询的 N 个客户端执行的命令。redis-benchmark提供默认的一组测试,或者我们可以提供自定义的一组测试。
语法redis 性能测试的基本命令如下:
1redis-benchmark [option] [option value]
注意:该命令是在 redis 的目录下执行的,而不是 redis 客户端的内部指令。
可选参数如下所示:
序号
选项
描述
默认值
1
-h
指定服务器主机名
127.0.0.1
2
-p
指定服务器端口
6379
3
-s
指定服务器 socket
4
-c
指定并发连接数
50
5
-n
指定请求数
10000
6
-d
以字节的形式指定 SET/GET 值的数据大小
3
7
-k
1=keep alive 0=reconnect
1
8
-r
SET/GET/INCR 使用随机 key, SADD 使用随机值
9
-P
通过管道传输 <numreq> ...