From 5d4bc085db4dd4fc853b8ad0c1738bb0030cdb97 Mon Sep 17 00:00:00 2001 From: nicksxs Date: Tue, 23 Aug 2022 23:50:50 +0800 Subject: [PATCH] Site updated: 2022-08-23 23:50:49 --- 2020/04/26/聊聊-mysql-的-MVCC/index.html | 2 +- .../聊聊-mysql-的-MVCC-续篇/index.html | 2 +- .../index.html | 2 +- .../index.html | 4 +- .../index.html | 4 +- .../index.html | 2 +- .../index.html | 2 +- .../index.html | 2 +- baidusitemap.xml | 8 +- categories/docker/index.html | 2 +- leancloud_counter_security_urls.json | 2 +- page/21/index.html | 2 +- page/22/index.html | 2 +- page/23/index.html | 2 +- page/26/index.html | 4 +- search.xml | 8616 ++++++++--------- sitemap.xml | 612 +- tags/DP/index.html | 2 +- tags/JVM/index.html | 2 +- tags/docker/index.html | 2 +- tags/java/page/2/index.html | 2 +- tags/linked-list/index.html | 2 +- tags/mysql/index.html | 2 +- tags/php/index.html | 2 +- 24 files changed, 4642 insertions(+), 4642 deletions(-) diff --git a/2020/04/26/聊聊-mysql-的-MVCC/index.html b/2020/04/26/聊聊-mysql-的-MVCC/index.html index 5e2a776156..c6d7591496 100644 --- a/2020/04/26/聊聊-mysql-的-MVCC/index.html +++ b/2020/04/26/聊聊-mysql-的-MVCC/index.html @@ -1,4 +1,4 @@ -聊聊 mysql 的 MVCC | Nicksxs's Blog

Nicksxs's Blog

What hurts more, the pain of hard work or the pain of regret?

0%

聊聊 mysql 的 MVCC

很久以前,有位面试官问到,你知道 mysql 的事务隔离级别吗,“额 O__O …,不太清楚”,完了之后我就去网上找相关的文章,找到了这篇MySQL 四种事务隔离级的说明, 文章写得特别好,看了这个就懂了各个事务隔离级别都是啥,不过看了这个之后多思考一下的话还是会发现问题,这么神奇的事务隔离级别是怎么实现的呢

其中 innodb 的事务隔离用到了标题里说到的 mvcc,Multiversion concurrency control, 直译过来就是多版本并发控制,先不讲这个究竟是个啥,考虑下如果纯猜测,这个事务隔离级别应该会是怎么样实现呢,愚钝的我想了下,可以在事务开始的时候拷贝一个表,这个可以支持 RR 级别,RC 级别就不支持了,而且要是个非常大的表,想想就不可行

腆着脸说虽然这个不可行,但是思路是对的,具体实行起来需要做一系列(肥肠多)的改动,首先根据我的理解,其实这个拷贝一个表是变成拷贝一条记录,但是如果有多个事务,那就得拷贝多次,这个问题其实可以借助版本管理系统来解释,在用版本管理系统,git 之类的之前,很原始的可能是开发完一个功能后,就打个压缩包用时间等信息命名,然后如果后面要找回这个就直接用这个压缩包的就行了,后来有了 svn,git 中心式和分布式的版本管理系统,它的一个特点是粒度可以控制到文件和代码行级别,对应的我们的 mysql 事务是不是也可以从一开始预想的表级别细化到行的级别,可能之前很多人都了解过,数据库的一行记录除了我们用户自定义的字段,还有一些额外的字段,去源码data0type.h里捞一下

/* Precise data types for system columns and the length of those columns;
+聊聊 mysql 的 MVCC | Nicksxs's Blog

Nicksxs's Blog

What hurts more, the pain of hard work or the pain of regret?

0%

聊聊 mysql 的 MVCC

很久以前,有位面试官问到,你知道 mysql 的事务隔离级别吗,“额 O__O …,不太清楚”,完了之后我就去网上找相关的文章,找到了这篇MySQL 四种事务隔离级的说明, 文章写得特别好,看了这个就懂了各个事务隔离级别都是啥,不过看了这个之后多思考一下的话还是会发现问题,这么神奇的事务隔离级别是怎么实现的呢

其中 innodb 的事务隔离用到了标题里说到的 mvcc,Multiversion concurrency control, 直译过来就是多版本并发控制,先不讲这个究竟是个啥,考虑下如果纯猜测,这个事务隔离级别应该会是怎么样实现呢,愚钝的我想了下,可以在事务开始的时候拷贝一个表,这个可以支持 RR 级别,RC 级别就不支持了,而且要是个非常大的表,想想就不可行

腆着脸说虽然这个不可行,但是思路是对的,具体实行起来需要做一系列(肥肠多)的改动,首先根据我的理解,其实这个拷贝一个表是变成拷贝一条记录,但是如果有多个事务,那就得拷贝多次,这个问题其实可以借助版本管理系统来解释,在用版本管理系统,git 之类的之前,很原始的可能是开发完一个功能后,就打个压缩包用时间等信息命名,然后如果后面要找回这个就直接用这个压缩包的就行了,后来有了 svn,git 中心式和分布式的版本管理系统,它的一个特点是粒度可以控制到文件和代码行级别,对应的我们的 mysql 事务是不是也可以从一开始预想的表级别细化到行的级别,可能之前很多人都了解过,数据库的一行记录除了我们用户自定义的字段,还有一些额外的字段,去源码data0type.h里捞一下

/* Precise data types for system columns and the length of those columns;
 NOTE: the values must run from 0 up in the order given! All codes must
 be less than 256 */
 #define DATA_ROW_ID 0     /* row id: a 48-bit integer */
diff --git a/2020/05/02/聊聊-mysql-的-MVCC-续篇/index.html b/2020/05/02/聊聊-mysql-的-MVCC-续篇/index.html
index 92964eda4b..7d0dc9fa77 100644
--- a/2020/05/02/聊聊-mysql-的-MVCC-续篇/index.html
+++ b/2020/05/02/聊聊-mysql-的-MVCC-续篇/index.html
@@ -1,4 +1,4 @@
-聊聊 mysql 的 MVCC 续篇 | Nicksxs's Blog

Nicksxs's Blog

What hurts more, the pain of hard work or the pain of regret?

0%

聊聊 mysql 的 MVCC 续篇

上一篇聊了mysql 的 innodb 引擎基于 read view 实现的 mvcc 和事务隔离级别,可能有些细心的小伙伴会发现一些问题,第一个是在 RC 级别下的事务提交后的可见性,这里涉及到了三个参数,m_low_limit_id,m_up_limit_id,m_ids,之前看到知乎的一篇写的非常不错的文章,但是就在这一点上似乎有点疑惑,这里基于源码和注释来解释下这个问题

/**
+聊聊 mysql 的 MVCC 续篇 | Nicksxs's Blog

Nicksxs's Blog

What hurts more, the pain of hard work or the pain of regret?

0%

聊聊 mysql 的 MVCC 续篇

上一篇聊了mysql 的 innodb 引擎基于 read view 实现的 mvcc 和事务隔离级别,可能有些细心的小伙伴会发现一些问题,第一个是在 RC 级别下的事务提交后的可见性,这里涉及到了三个参数,m_low_limit_id,m_up_limit_id,m_ids,之前看到知乎的一篇写的非常不错的文章,但是就在这一点上似乎有点疑惑,这里基于源码和注释来解释下这个问题

/**
 Opens a read view where exactly the transactions serialized before this
 point in time are seen in the view.
 @param id		Creator transaction id */
diff --git a/2020/05/10/聊聊-mysql-的-MVCC-续续篇之加锁分析/index.html b/2020/05/10/聊聊-mysql-的-MVCC-续续篇之加锁分析/index.html
index cf0672f794..1b71a9c678 100644
--- a/2020/05/10/聊聊-mysql-的-MVCC-续续篇之加锁分析/index.html
+++ b/2020/05/10/聊聊-mysql-的-MVCC-续续篇之加锁分析/index.html
@@ -1 +1 @@
-聊聊 mysql 的 MVCC 续续篇之锁分析 | Nicksxs's Blog

Nicksxs's Blog

What hurts more, the pain of hard work or the pain of regret?

0%

聊聊 mysql 的 MVCC 续续篇之锁分析

看完前面两篇水文之后,感觉不得不来分析下 mysql 的锁了,其实前面说到幻读的时候是有个前提没提到的,比如一个select * from table1 where id = 1这种查询语句其实是不会加传说中的锁的,当然这里是指在 RR 或者 RC 隔离级别下,
看一段 mysql官方文档

SELECT ... FROM is a consistent read, reading a snapshot of the database and setting no locks unless the transaction isolation level is set to SERIALIZABLE. For SERIALIZABLE level, the search sets shared next-key locks on the index records it encounters. However, only an index record lock is required for statements that lock rows using a unique index to search for a unique row.

纯粹的这种一致性读,实际读取的是快照,也就是基于 read view 的读取方式,除非当前隔离级别是SERIALIZABLE
但是对于以下几类

  • select * from table where ? lock in share mode;
  • select * from table where ? for update;
  • insert into table values (...);
  • update table set ? where ?;
  • delete from table where ?;

除了第一条是 S 锁之外,其他都是 X 排他锁,这边在顺带下,S 锁表示共享锁, X 表示独占锁,同为 S 锁之间不冲突,S 与 X,X 与 S,X 与 X 之间都冲突,也就是加了前者,后者就加不上了
我们知道对于 RC 级别会出现幻读现象,对于 RR 级别不会出现,主要的区别是 RR 级别下对于以上的加锁读取都根据情况加上了 gap 锁,那么是不是 RR 级别下以上所有的都是要加 gap 锁呢,当然不是
举个例子,RR 事务隔离级别下,table1 有个主键id 字段
select * from table1 where id = 10 for update
这条语句要加 gap 锁吗?
答案是不需要,这里其实算是我看了这么久的一点自己的理解,啥时候要加 gap 锁,判断的条件是根据我查询的数据是否会因为不加 gap 锁而出现数量的不一致,我上面这条查询语句,在什么情况下会出现查询结果数量不一致呢,只要在这条记录被更新或者删除的时候,有没有可能我第一次查出来一条,第二次变成两条了呢,不可能,因为是主键索引。
再变更下这个题的条件,当 id 不是主键,但是是唯一索引,这样需要怎么加锁,注意问题是怎么加锁,不是需不需要加 gap 锁,这里呢就是稍微延伸一下,把聚簇索引(主键索引)和二级索引带一下,当 id 不是主键,说明是个二级索引,但是它是唯一索引,体会下,首先对于 id = 10这个二级索引肯定要加锁,要不要锁 gap 呢,不用,因为是唯一索引,id = 10 只可能有这一条记录,然后呢,这样是不是就好了,还不行,因为啥,因为它是二级索引,对应的主键索引的记录才是真正的数据,万一被更新掉了咋办,所以在 id = 10 对应的主键索引上也需要加上锁(默认都是 record lock行锁),那主键索引上要不要加 gap 呢,也不用,也是精确定位到这一条记录
最后呢,当 id 不是主键,也不是唯一索引,只是个普通的索引,这里就需要大名鼎鼎的 gap 锁了,
是时候画个图了

其实核心的目的还是不让这个 id=10 的记录不会出现幻读,那么就需要在 id 这个索引上加上三个 gap 锁,主键索引上就不用了,在 id 索引上已经控制住了id = 10 不会出现幻读,主键索引上这两条对应的记录已经锁了,所以就这样 OK 了

\ No newline at end of file +聊聊 mysql 的 MVCC 续续篇之锁分析 | Nicksxs's Blog

Nicksxs's Blog

What hurts more, the pain of hard work or the pain of regret?

0%

聊聊 mysql 的 MVCC 续续篇之锁分析

看完前面两篇水文之后,感觉不得不来分析下 mysql 的锁了,其实前面说到幻读的时候是有个前提没提到的,比如一个select * from table1 where id = 1这种查询语句其实是不会加传说中的锁的,当然这里是指在 RR 或者 RC 隔离级别下,
看一段 mysql官方文档

SELECT ... FROM is a consistent read, reading a snapshot of the database and setting no locks unless the transaction isolation level is set to SERIALIZABLE. For SERIALIZABLE level, the search sets shared next-key locks on the index records it encounters. However, only an index record lock is required for statements that lock rows using a unique index to search for a unique row.

纯粹的这种一致性读,实际读取的是快照,也就是基于 read view 的读取方式,除非当前隔离级别是SERIALIZABLE
但是对于以下几类

  • select * from table where ? lock in share mode;
  • select * from table where ? for update;
  • insert into table values (...);
  • update table set ? where ?;
  • delete from table where ?;

除了第一条是 S 锁之外,其他都是 X 排他锁,这边在顺带下,S 锁表示共享锁, X 表示独占锁,同为 S 锁之间不冲突,S 与 X,X 与 S,X 与 X 之间都冲突,也就是加了前者,后者就加不上了
我们知道对于 RC 级别会出现幻读现象,对于 RR 级别不会出现,主要的区别是 RR 级别下对于以上的加锁读取都根据情况加上了 gap 锁,那么是不是 RR 级别下以上所有的都是要加 gap 锁呢,当然不是
举个例子,RR 事务隔离级别下,table1 有个主键id 字段
select * from table1 where id = 10 for update
这条语句要加 gap 锁吗?
答案是不需要,这里其实算是我看了这么久的一点自己的理解,啥时候要加 gap 锁,判断的条件是根据我查询的数据是否会因为不加 gap 锁而出现数量的不一致,我上面这条查询语句,在什么情况下会出现查询结果数量不一致呢,只要在这条记录被更新或者删除的时候,有没有可能我第一次查出来一条,第二次变成两条了呢,不可能,因为是主键索引。
再变更下这个题的条件,当 id 不是主键,但是是唯一索引,这样需要怎么加锁,注意问题是怎么加锁,不是需不需要加 gap 锁,这里呢就是稍微延伸一下,把聚簇索引(主键索引)和二级索引带一下,当 id 不是主键,说明是个二级索引,但是它是唯一索引,体会下,首先对于 id = 10这个二级索引肯定要加锁,要不要锁 gap 呢,不用,因为是唯一索引,id = 10 只可能有这一条记录,然后呢,这样是不是就好了,还不行,因为啥,因为它是二级索引,对应的主键索引的记录才是真正的数据,万一被更新掉了咋办,所以在 id = 10 对应的主键索引上也需要加上锁(默认都是 record lock行锁),那主键索引上要不要加 gap 呢,也不用,也是精确定位到这一条记录
最后呢,当 id 不是主键,也不是唯一索引,只是个普通的索引,这里就需要大名鼎鼎的 gap 锁了,
是时候画个图了

其实核心的目的还是不让这个 id=10 的记录不会出现幻读,那么就需要在 id 这个索引上加上三个 gap 锁,主键索引上就不用了,在 id 索引上已经控制住了id = 10 不会出现幻读,主键索引上这两条对应的记录已经锁了,所以就这样 OK 了

\ No newline at end of file diff --git a/2020/06/26/聊一下-RocketMQ-的-Consumer/index.html b/2020/06/26/聊一下-RocketMQ-的-Consumer/index.html index bc1ceaefbc..7ba50281c8 100644 --- a/2020/06/26/聊一下-RocketMQ-的-Consumer/index.html +++ b/2020/06/26/聊一下-RocketMQ-的-Consumer/index.html @@ -1,4 +1,4 @@ -聊一下 RocketMQ 的 DefaultMQPushConsumer 源码 | Nicksxs's Blog

Nicksxs's Blog

What hurts more, the pain of hard work or the pain of regret?

0%

聊一下 RocketMQ 的 DefaultMQPushConsumer 源码

首先看下官方的小 demo

public static void main(String[] args) throws InterruptedException, MQClientException {
+聊一下 RocketMQ 的 DefaultMQPushConsumer 源码 | Nicksxs's Blog

Nicksxs's Blog

What hurts more, the pain of hard work or the pain of regret?

0%

聊一下 RocketMQ 的 DefaultMQPushConsumer 源码

首先看下官方的小 demo

public static void main(String[] args) throws InterruptedException, MQClientException {
 
         /*
          * Instantiate with specified consumer group name.
@@ -737,4 +737,4 @@
                 throw new RemotingTimeoutException(info);
             }
         }
-    }
\ No newline at end of file + }
\ No newline at end of file diff --git a/2020/07/05/聊一下-RocketMQ-的-NameServer-源码/index.html b/2020/07/05/聊一下-RocketMQ-的-NameServer-源码/index.html index ad73660cf5..a36f692966 100644 --- a/2020/07/05/聊一下-RocketMQ-的-NameServer-源码/index.html +++ b/2020/07/05/聊一下-RocketMQ-的-NameServer-源码/index.html @@ -1,4 +1,4 @@ -聊一下 RocketMQ 的 NameServer 源码 | Nicksxs's Blog

Nicksxs's Blog

What hurts more, the pain of hard work or the pain of regret?

0%

聊一下 RocketMQ 的 NameServer 源码

前面介绍了,nameserver相当于dubbo的注册中心,用与管理broker,broker会在启动的时候注册到nameserver,并且会发送心跳给namaserver,nameserver负责保存活跃的broker,包括master和slave,同时保存topic和topic下的队列,以及filter列表,然后为producer和consumer的请求提供服务。

启动过程

public static void main(String[] args) {
+聊一下 RocketMQ 的 NameServer 源码 | Nicksxs's Blog

Nicksxs's Blog

What hurts more, the pain of hard work or the pain of regret?

0%

聊一下 RocketMQ 的 NameServer 源码

前面介绍了,nameserver相当于dubbo的注册中心,用与管理broker,broker会在启动的时候注册到nameserver,并且会发送心跳给namaserver,nameserver负责保存活跃的broker,包括master和slave,同时保存topic和topic下的队列,以及filter列表,然后为producer和consumer的请求提供服务。

启动过程

public static void main(String[] args) {
         main0(args);
     }
 
@@ -563,4 +563,4 @@
         response.setRemark("No topic route info in name server for the topic: " + requestHeader.getTopic()
             + FAQUrl.suggestTodo(FAQUrl.APPLY_TOPIC_URL));
         return response;
-    }

首先调用org.apache.rocketmq.namesrv.routeinfo.RouteInfoManager#pickupTopicRouteDataorg.apache.rocketmq.namesrv.routeinfo.RouteInfoManager#topicQueueTable获取到org.apache.rocketmq.common.protocol.route.QueueData这里面存了 brokerName,再通过org.apache.rocketmq.namesrv.routeinfo.RouteInfoManager#brokerAddrTable里获取到 broker 的地址信息等,然后再获取 orderMessage 的配置。

简要分析了下 RocketMQ 的 NameServer 的代码,比较粗粒度。

\ No newline at end of file + }

首先调用org.apache.rocketmq.namesrv.routeinfo.RouteInfoManager#pickupTopicRouteDataorg.apache.rocketmq.namesrv.routeinfo.RouteInfoManager#topicQueueTable获取到org.apache.rocketmq.common.protocol.route.QueueData这里面存了 brokerName,再通过org.apache.rocketmq.namesrv.routeinfo.RouteInfoManager#brokerAddrTable里获取到 broker 的地址信息等,然后再获取 orderMessage 的配置。

简要分析了下 RocketMQ 的 NameServer 的代码,比较粗粒度。

\ No newline at end of file diff --git a/2020/08/06/Linux-下-grep-命令的一点小技巧/index.html b/2020/08/06/Linux-下-grep-命令的一点小技巧/index.html index f2359b69c8..771eed2ad0 100644 --- a/2020/08/06/Linux-下-grep-命令的一点小技巧/index.html +++ b/2020/08/06/Linux-下-grep-命令的一点小技巧/index.html @@ -1,4 +1,4 @@ -Linux 下 grep 命令的一点小技巧 | Nicksxs's Blog

Nicksxs's Blog

What hurts more, the pain of hard work or the pain of regret?

0%

Linux 下 grep 命令的一点小技巧

用了比较久的 grep 命令,其实都只是用了最最基本的功能来查日志,

譬如


+Linux 下 grep 命令的一点小技巧 | Nicksxs's Blog

Nicksxs's Blog

What hurts more, the pain of hard work or the pain of regret?

0%

Linux 下 grep 命令的一点小技巧

用了比较久的 grep 命令,其实都只是用了最最基本的功能来查日志,

譬如


 grep 'xxx' xxxx.log
 

然后有挺多情况比如想要找日志里带一些符号什么的,就需要用到一些特殊的

比如这样\"userId\":\"123456\",因为比如用户 ID 有时候会跟其他的 id 一样,只用具体的值 123456 来查的话干扰信息太多了,如果直接这样


 grep '\"userId\":\"123456\"' xxxx.log
diff --git a/2020/09/06/mybatis-的-和-是有啥区别/index.html b/2020/09/06/mybatis-的-和-是有啥区别/index.html
index ad598b7e9d..2d72312bb7 100644
--- a/2020/09/06/mybatis-的-和-是有啥区别/index.html
+++ b/2020/09/06/mybatis-的-和-是有啥区别/index.html
@@ -1,4 +1,4 @@
-mybatis 的 $ 和 # 是有啥区别 | Nicksxs's Blog

Nicksxs's Blog

What hurts more, the pain of hard work or the pain of regret?

0%

mybatis 的 $ 和 # 是有啥区别

这个问题也是面试中常被问到的,就抽空来了解下这个,跳过一大段前面初始化的逻辑,
对于一条select * from t1 where id = #{id}这样的 sql,在初始化扫描 mapper 的xml文件的时候会根据是否是 dynamic 来判断生成 DynamicSqlSource 还是 RawSqlSource,这里它是一条 RawSqlSource,
在这里做了替换,将#{}替换成了?

前面说的是否 dynamic 就是在这里进行判断

// org.apache.ibatis.scripting.xmltags.XMLScriptBuilder#parseScriptNode
+mybatis 的 $ 和 # 是有啥区别 | Nicksxs's Blog

Nicksxs's Blog

What hurts more, the pain of hard work or the pain of regret?

0%

mybatis 的 $ 和 # 是有啥区别

这个问题也是面试中常被问到的,就抽空来了解下这个,跳过一大段前面初始化的逻辑,
对于一条select * from t1 where id = #{id}这样的 sql,在初始化扫描 mapper 的xml文件的时候会根据是否是 dynamic 来判断生成 DynamicSqlSource 还是 RawSqlSource,这里它是一条 RawSqlSource,
在这里做了替换,将#{}替换成了?

前面说的是否 dynamic 就是在这里进行判断

// org.apache.ibatis.scripting.xmltags.XMLScriptBuilder#parseScriptNode
 public SqlSource parseScriptNode() {
     MixedSqlNode rootSqlNode = parseDynamicTags(context);
     SqlSource sqlSource;
diff --git a/2020/10/03/mybatis-的缓存是怎么回事/index.html b/2020/10/03/mybatis-的缓存是怎么回事/index.html
index 468da4c03f..00be10305a 100644
--- a/2020/10/03/mybatis-的缓存是怎么回事/index.html
+++ b/2020/10/03/mybatis-的缓存是怎么回事/index.html
@@ -1,4 +1,4 @@
-mybatis 的缓存是怎么回事 | Nicksxs's Blog

Nicksxs's Blog

What hurts more, the pain of hard work or the pain of regret?

0%

mybatis 的缓存是怎么回事

Java 真的是任何一个中间件,比较常用的那种,都有很多内容值得深挖,比如这个缓存,慢慢有过一些感悟,比如如何提升性能,缓存无疑是一大重要手段,最底层开始 CPU 就有缓存,而且又小又贵,再往上一点内存一般作为硬盘存储在运行时的存储,一般在代码里也会用内存作为一些本地缓存,譬如数据库,像 mysql 这种也是有innodb_buffer_pool来提升查询效率,本质上理解就是用更快的存储作为相对慢存储的缓存,减少查询直接访问较慢的存储,并且这个都是相对的,比起 cpu 的缓存,那内存也是渣,但是与普通机械硬盘相比,那也是两个次元的水平。

闲扯这么多来说说 mybatis 的缓存,mybatis 一般作为一个轻量级的 orm 使用,相对应的就是比较重量级的 hibernate,不过不在这次讨论范围,上一次是主要讲了 mybatis 在解析 sql 过程中,对于两种占位符的不同替换实现策略,这次主要聊下 mybatis 的缓存,前面其实得了解下前置的东西,比如 sqlsession,先当做我们知道 sqlsession 是个什么玩意,可能或多或少的知道 mybatis 是有两级缓存,

一级缓存

第一级的缓存是在 BaseExecutor 中的 PerpetualCache,它是个最基本的缓存实现类,使用了 HashMap 实现缓存功能,代码其实没几十行

public class PerpetualCache implements Cache {
+mybatis 的缓存是怎么回事 | Nicksxs's Blog

Nicksxs's Blog

What hurts more, the pain of hard work or the pain of regret?

0%

mybatis 的缓存是怎么回事

Java 真的是任何一个中间件,比较常用的那种,都有很多内容值得深挖,比如这个缓存,慢慢有过一些感悟,比如如何提升性能,缓存无疑是一大重要手段,最底层开始 CPU 就有缓存,而且又小又贵,再往上一点内存一般作为硬盘存储在运行时的存储,一般在代码里也会用内存作为一些本地缓存,譬如数据库,像 mysql 这种也是有innodb_buffer_pool来提升查询效率,本质上理解就是用更快的存储作为相对慢存储的缓存,减少查询直接访问较慢的存储,并且这个都是相对的,比起 cpu 的缓存,那内存也是渣,但是与普通机械硬盘相比,那也是两个次元的水平。

闲扯这么多来说说 mybatis 的缓存,mybatis 一般作为一个轻量级的 orm 使用,相对应的就是比较重量级的 hibernate,不过不在这次讨论范围,上一次是主要讲了 mybatis 在解析 sql 过程中,对于两种占位符的不同替换实现策略,这次主要聊下 mybatis 的缓存,前面其实得了解下前置的东西,比如 sqlsession,先当做我们知道 sqlsession 是个什么玩意,可能或多或少的知道 mybatis 是有两级缓存,

一级缓存

第一级的缓存是在 BaseExecutor 中的 PerpetualCache,它是个最基本的缓存实现类,使用了 HashMap 实现缓存功能,代码其实没几十行

public class PerpetualCache implements Cache {
 
   private final String id;
 
diff --git a/baidusitemap.xml b/baidusitemap.xml
index a06da14188..750f7a67be 100644
--- a/baidusitemap.xml
+++ b/baidusitemap.xml
@@ -580,10 +580,6 @@
     https://nicksxs.me/2015/04/14/Add-Two-Number/
     2020-01-12
   
-  
-    https://nicksxs.me/2014/12/24/MFC%20%E6%A8%A1%E6%80%81%E5%AF%B9%E8%AF%9D%E6%A1%86/
-    2020-01-12
-  
   
     https://nicksxs.me/2019/12/10/Redis-Part-1/
     2020-01-12
@@ -604,6 +600,10 @@
     https://nicksxs.me/2017/05/09/ambari-summary/
     2020-01-12
   
+  
+    https://nicksxs.me/2014/12/24/MFC%20%E6%A8%A1%E6%80%81%E5%AF%B9%E8%AF%9D%E6%A1%86/
+    2020-01-12
+  
   
     https://nicksxs.me/2016/09/29/binary-watch/
     2020-01-12
diff --git a/categories/docker/index.html b/categories/docker/index.html
index 74a010b104..aacbf631d4 100644
--- a/categories/docker/index.html
+++ b/categories/docker/index.html
@@ -1 +1 @@
-分类: Docker | Nicksxs's Blog

Nicksxs's Blog

What hurts more, the pain of hard work or the pain of regret?

0%
\ No newline at end of file +分类: docker | Nicksxs's Blog

Nicksxs's Blog

What hurts more, the pain of hard work or the pain of regret?

0%

docker 分类

2016
\ No newline at end of file diff --git a/leancloud_counter_security_urls.json b/leancloud_counter_security_urls.json index 833841a361..e05e3a5eda 100644 --- a/leancloud_counter_security_urls.json +++ b/leancloud_counter_security_urls.json @@ -1 +1 @@ -[{"title":"村上春树《1Q84》读后感","url":"/2019/12/18/1Q84读后感/"},{"title":"2019年终总结","url":"/2020/02/01/2019年终总结/"},{"title":"2020 年终总结","url":"/2021/03/31/2020-年终总结/"},{"title":"2020年中总结","url":"/2020/07/11/2020年中总结/"},{"title":"2021 年中总结","url":"/2021/07/18/2021-年中总结/"},{"title":"34_Search_for_a_Range","url":"/2016/08/14/34-Search-for-a-Range/"},{"title":"2021 年终总结","url":"/2022/01/22/2021-年终总结/"},{"title":"AQS篇二 之 Condition 浅析笔记","url":"/2021/02/21/AQS-之-Condition-浅析笔记/"},{"title":"add-two-number","url":"/2015/04/14/Add-Two-Number/"},{"title":"Apollo 的 value 注解是怎么自动更新的","url":"/2020/11/01/Apollo-的-value-注解是怎么自动更新的/"},{"title":"AbstractQueuedSynchronizer","url":"/2019/09/23/AbstractQueuedSynchronizer/"},{"title":"AQS篇一","url":"/2021/02/14/AQS篇一/"},{"title":"Clone Graph Part I","url":"/2014/12/30/Clone-Graph-Part-I/"},{"title":"Comparator使用小记","url":"/2020/04/05/Comparator使用小记/"},{"title":"Disruptor 系列一","url":"/2022/02/13/Disruptor-系列一/"},{"title":"Disruptor 系列二","url":"/2022/02/27/Disruptor-系列二/"},{"title":"Filter, Interceptor, Aop, 啥, 啥, 啥? 这些都是啥?","url":"/2020/08/22/Filter-Intercepter-Aop-啥-啥-啥-这些都是啥/"},{"title":"JVM源码分析之G1垃圾收集器分析一","url":"/2019/12/07/JVM-G1-Part-1/"},{"title":"G1收集器概述","url":"/2020/02/09/G1收集器概述/"},{"title":"Dubbo 使用的几个记忆点","url":"/2022/04/02/Dubbo-使用的几个记忆点/"},{"title":"Leetcode 021 合并两个有序链表 ( Merge Two Sorted Lists ) 题解分析","url":"/2021/10/07/Leetcode-021-合并两个有序链表-Merge-Two-Sorted-Lists-题解分析/"},{"title":"Leetcode 028 实现 strStr() ( Implement strStr() ) 题解分析","url":"/2021/10/31/Leetcode-028-实现-strStr-Implement-strStr-题解分析/"},{"title":"Leetcode 053 最大子序和 ( Maximum Subarray ) 题解分析","url":"/2021/11/28/Leetcode-053-最大子序和-Maximum-Subarray-题解分析/"},{"title":"Leetcode 104 二叉树的最大深度(Maximum Depth of Binary Tree) 题解分析","url":"/2020/10/25/Leetcode-104-二叉树的最大深度-Maximum-Depth-of-Binary-Tree-题解分析/"},{"title":"Leetcode 105 从前序与中序遍历序列构造二叉树(Construct Binary Tree from Preorder and Inorder Traversal) 题解分析","url":"/2020/12/13/Leetcode-105-从前序与中序遍历序列构造二叉树-Construct-Binary-Tree-from-Preorder-and-Inorder-Traversal-题解分析/"},{"title":"Leetcode 121 买卖股票的最佳时机(Best Time to Buy and Sell Stock) 题解分析","url":"/2021/03/14/Leetcode-121-买卖股票的最佳时机-Best-Time-to-Buy-and-Sell-Stock-题解分析/"},{"title":"Leetcode 124 二叉树中的最大路径和(Binary Tree Maximum Path Sum) 题解分析","url":"/2021/01/24/Leetcode-124-二叉树中的最大路径和-Binary-Tree-Maximum-Path-Sum-题解分析/"},{"title":"Leetcode 155 最小栈(Min Stack) 题解分析","url":"/2020/12/06/Leetcode-155-最小栈-Min-Stack-题解分析/"},{"title":"Leetcode 160 相交链表(intersection-of-two-linked-lists) 题解分析","url":"/2021/01/10/Leetcode-160-相交链表-intersection-of-two-linked-lists-题解分析/"},{"title":"Leetcode 2 Add Two Numbers 题解分析","url":"/2020/10/11/Leetcode-2-Add-Two-Numbers-题解分析/"},{"title":"Leetcode 1115 交替打印 FooBar ( Print FooBar Alternately *Medium* ) 题解分析","url":"/2022/05/01/Leetcode-1115-交替打印-FooBar-Print-FooBar-Alternately-Medium-题解分析/"},{"title":"Leetcode 234 回文链表(Palindrome Linked List) 题解分析","url":"/2020/11/15/Leetcode-234-回文联表-Palindrome-Linked-List-题解分析/"},{"title":"Leetcode 236 二叉树的最近公共祖先(Lowest Common Ancestor of a Binary Tree) 题解分析","url":"/2021/05/23/Leetcode-236-二叉树的最近公共祖先-Lowest-Common-Ancestor-of-a-Binary-Tree-题解分析/"},{"title":"Leetcode 278 第一个错误的版本 ( First Bad Version *Easy* ) 题解分析","url":"/2022/08/14/Leetcode-278-第一个错误的版本-First-Bad-Version-Easy-题解分析/"},{"title":"Leetcode 3 Longest Substring Without Repeating Characters 题解分析","url":"/2020/09/20/Leetcode-3-Longest-Substring-Without-Repeating-Characters-题解分析/"},{"title":"Leetcode 1260 二维网格迁移 ( Shift 2D Grid *Easy* ) 题解分析","url":"/2022/07/22/Leetcode-1260-二维网格迁移-Shift-2D-Grid-Easy-题解分析/"},{"title":"Leetcode 4 寻找两个正序数组的中位数 ( Median of Two Sorted Arrays *Hard* ) 题解分析","url":"/2022/03/27/Leetcode-4-寻找两个正序数组的中位数-Median-of-Two-Sorted-Arrays-Hard-题解分析/"},{"title":"Leetcode 42 接雨水 (Trapping Rain Water) 题解分析","url":"/2021/07/04/Leetcode-42-接雨水-Trapping-Rain-Water-题解分析/"},{"title":"Leetcode 16 最接近的三数之和 ( 3Sum Closest *Medium* ) 题解分析","url":"/2022/08/06/Leetcode-16-最接近的三数之和-3Sum-Closest-Medium-题解分析/"},{"title":"Leetcode 48 旋转图像(Rotate Image) 题解分析","url":"/2021/05/01/Leetcode-48-旋转图像-Rotate-Image-题解分析/"},{"title":"Leetcode 20 有效的括号 ( Valid Parentheses *Easy* ) 题解分析","url":"/2022/07/02/Leetcode-20-有效的括号-Valid-Parentheses-Easy-题解分析/"},{"title":"leetcode no.3","url":"/2015/04/15/Leetcode-No-3/"},{"title":"Leetcode 885 螺旋矩阵 III ( Spiral Matrix III *Medium* ) 题解分析","url":"/2022/08/23/Leetcode-885-螺旋矩阵-III-Spiral-Matrix-III-Medium-题解分析/"},{"title":"Leetcode 349 两个数组的交集 ( Intersection of Two Arrays *Easy* ) 题解分析","url":"/2022/03/07/Leetcode-349-两个数组的交集-Intersection-of-Two-Arrays-Easy-题解分析/"},{"title":"MFC 模态对话框","url":"/2014/12/24/MFC 模态对话框/"},{"title":"Leetcode 83 删除排序链表中的重复元素 ( Remove Duplicates from Sorted List *Easy* ) 题解分析","url":"/2022/03/13/Leetcode-83-删除排序链表中的重复元素-Remove-Duplicates-from-Sorted-List-Easy-题解分析/"},{"title":"Linux 下 grep 命令的一点小技巧","url":"/2020/08/06/Linux-下-grep-命令的一点小技巧/"},{"title":"Leetcode 698 划分为k个相等的子集 ( Partition to K Equal Sum Subsets *Medium* ) 题解分析","url":"/2022/06/19/Leetcode-698-划分为k个相等的子集-Partition-to-K-Equal-Sum-Subsets-Medium-题解分析/"},{"title":"Maven实用小技巧","url":"/2020/02/16/Maven实用小技巧/"},{"title":"Number of 1 Bits","url":"/2015/03/11/Number-Of-1-Bits/"},{"title":"Path Sum","url":"/2015/01/04/Path-Sum/"},{"title":"Redis_分布式锁","url":"/2019/12/10/Redis-Part-1/"},{"title":"Reverse Bits","url":"/2015/03/11/Reverse-Bits/"},{"title":"Reverse Integer","url":"/2015/03/13/Reverse-Integer/"},{"title":"two sum","url":"/2015/01/14/Two-Sum/"},{"title":"ambari-summary","url":"/2017/05/09/ambari-summary/"},{"title":"binary-watch","url":"/2016/09/29/binary-watch/"},{"title":"docker-mysql-cluster","url":"/2016/08/14/docker-mysql-cluster/"},{"title":"docker比一般多一点的初学者介绍","url":"/2020/03/08/docker比一般多一点的初学者介绍/"},{"title":"docker比一般多一点的初学者介绍三","url":"/2020/03/21/docker比一般多一点的初学者介绍三/"},{"title":"docker比一般多一点的初学者介绍二","url":"/2020/03/15/docker比一般多一点的初学者介绍二/"},{"title":"docker使用中发现的echo命令的一个小技巧及其他","url":"/2020/03/29/echo命令的一个小技巧/"},{"title":"gogs使用webhook部署react单页应用","url":"/2020/02/22/gogs使用webhook部署react单页应用/"},{"title":"invert-binary-tree","url":"/2015/06/22/invert-binary-tree/"},{"title":"minimum-size-subarray-sum-209","url":"/2016/10/11/minimum-size-subarray-sum-209/"},{"title":"C++ 指针使用中的一个小问题","url":"/2014/12/23/my-new-post/"},{"title":"mybatis 的 $ 和 # 是有啥区别","url":"/2020/09/06/mybatis-的-和-是有啥区别/"},{"title":"mybatis 的缓存是怎么回事","url":"/2020/10/03/mybatis-的缓存是怎么回事/"},{"title":"openresty","url":"/2019/06/18/openresty/"},{"title":"dubbo 客户端配置的一个重要知识点","url":"/2022/06/11/dubbo-客户端配置的一个重要知识点/"},{"title":"pcre-intro-and-a-simple-package","url":"/2015/01/16/pcre-intro-and-a-simple-package/"},{"title":"php-abstract-class-and-interface","url":"/2016/11/10/php-abstract-class-and-interface/"},{"title":"rabbitmq-tips","url":"/2017/04/25/rabbitmq-tips/"},{"title":"redis 的 rdb 和 COW 介绍","url":"/2021/08/15/redis-的-rdb-和-COW-介绍/"},{"title":"redis数据结构介绍-第一部分 SDS,链表,字典","url":"/2019/12/26/redis数据结构介绍/"},{"title":"mybatis 的 foreach 使用的注意点","url":"/2022/07/09/mybatis-的-foreach-使用的注意点/"},{"title":"redis数据结构介绍三-第三部分 整数集合","url":"/2020/01/10/redis数据结构介绍三/"},{"title":"redis数据结构介绍二-第二部分 跳表","url":"/2020/01/04/redis数据结构介绍二/"},{"title":"redis数据结构介绍五-第五部分 对象","url":"/2020/01/20/redis数据结构介绍五/"},{"title":"redis数据结构介绍六 快表","url":"/2020/01/22/redis数据结构介绍六/"},{"title":"redis数据结构介绍四-第四部分 压缩表","url":"/2020/01/19/redis数据结构介绍四/"},{"title":"redis淘汰策略复习","url":"/2021/08/01/redis淘汰策略复习/"},{"title":"nginx 日志小记","url":"/2022/04/17/nginx-日志小记/"},{"title":"redis系列介绍七-过期策略","url":"/2020/04/12/redis系列介绍七/"},{"title":"redis过期策略复习","url":"/2021/07/25/redis过期策略复习/"},{"title":"redis系列介绍八-淘汰策略","url":"/2020/04/18/redis系列介绍八/"},{"title":"rust学习笔记-所有权三之切片","url":"/2021/05/16/rust学习笔记-所有权三之切片/"},{"title":"rust学习笔记-所有权二","url":"/2021/04/18/rust学习笔记-所有权二/"},{"title":"rust学习笔记-所有权一","url":"/2021/04/18/rust学习笔记/"},{"title":"spark-little-tips","url":"/2017/03/28/spark-little-tips/"},{"title":"spring event 介绍","url":"/2022/01/30/spring-event-介绍/"},{"title":"summary-ranges-228","url":"/2016/10/12/summary-ranges-228/"},{"title":"wordpress 忘记密码的一种解决方法","url":"/2021/12/05/wordpress-忘记密码的一种解决方法/"},{"title":"swoole-websocket-test","url":"/2016/07/13/swoole-websocket-test/"},{"title":"《垃圾回收算法手册读书》笔记之整理算法","url":"/2021/03/07/《垃圾回收算法手册读书》笔记之整理算法/"},{"title":"一个 nginx 的简单记忆点","url":"/2022/08/21/一个-nginx-的简单记忆点/"},{"title":"《长安的荔枝》读后感","url":"/2022/07/17/《长安的荔枝》读后感/"},{"title":"介绍一下 RocketMQ","url":"/2020/06/21/介绍一下-RocketMQ/"},{"title":"介绍下最近比较实用的端口转发","url":"/2021/11/14/介绍下最近比较实用的端口转发/"},{"title":"从丁仲礼被美国制裁聊点啥","url":"/2020/12/20/从丁仲礼被美国制裁聊点啥/"},{"title":"上次的其他 外行聊国足","url":"/2022/03/06/上次的其他-外行聊国足/"},{"title":"关于公共交通再吐个槽","url":"/2021/03/21/关于公共交通再吐个槽/"},{"title":"从清华美院学姐聊聊我们身边的恶人","url":"/2020/11/29/从清华美院学姐聊聊我们身边的恶人/"},{"title":"关于读书打卡与分享","url":"/2021/02/07/关于读书打卡与分享/"},{"title":"周末我在老丈人家打了天小工","url":"/2020/08/16/周末我在老丈人家打了天小工/"},{"title":"分享记录一下一个 scp 操作方法","url":"/2022/02/06/分享记录一下一个-scp-操作方法/"},{"title":"在老丈人家的小工记三","url":"/2020/09/13/在老丈人家的小工记三/"},{"title":"在老丈人家的小工记五","url":"/2020/10/18/在老丈人家的小工记五/"},{"title":"分享记录一下一个 git 操作方法","url":"/2022/02/06/分享记录一下一个-git-操作方法/"},{"title":"在老丈人家的小工记四","url":"/2020/09/26/在老丈人家的小工记四/"},{"title":"寄生虫观后感","url":"/2020/03/01/寄生虫观后感/"},{"title":"我是如何走上跑步这条不归路的","url":"/2020/07/26/我是如何走上跑步这条不归路的/"},{"title":"看完了扫黑风暴,聊聊感想","url":"/2021/10/24/看完了扫黑风暴-聊聊感想/"},{"title":"搬运两个 StackOverflow 上的 Mysql 编码相关的问题解答","url":"/2022/01/16/搬运两个-StackOverflow-上的-Mysql-编码相关的问题解答/"},{"title":"聊一下 RocketMQ 的 NameServer 源码","url":"/2020/07/05/聊一下-RocketMQ-的-NameServer-源码/"},{"title":"给小电驴上牌","url":"/2022/03/20/给小电驴上牌/"},{"title":"聊一下 RocketMQ 的 DefaultMQPushConsumer 源码","url":"/2020/06/26/聊一下-RocketMQ-的-Consumer/"},{"title":"聊一下 RocketMQ 的消息存储之 MMAP","url":"/2021/09/04/聊一下-RocketMQ-的消息存储/"},{"title":"聊一下 RocketMQ 的消息存储三","url":"/2021/10/03/聊一下-RocketMQ-的消息存储三/"},{"title":"是何原因竟让两人深夜奔袭十公里","url":"/2022/06/05/是何原因竟让两人深夜奔袭十公里/"},{"title":"屯菜惊魂记","url":"/2022/04/24/屯菜惊魂记/"},{"title":"聊一下 RocketMQ 的消息存储四","url":"/2021/10/17/聊一下-RocketMQ-的消息存储四/"},{"title":"聊一下 RocketMQ 的消息存储二","url":"/2021/09/12/聊一下-RocketMQ-的消息存储二/"},{"title":"聊一下 RocketMQ 的顺序消息","url":"/2021/08/29/聊一下-RocketMQ-的顺序消息/"},{"title":"聊一下 SpringBoot 中使用的 cglib 作为动态代理中的一个注意点","url":"/2021/09/19/聊一下-SpringBoot-中使用的-cglib-作为动态代理中的一个注意点/"},{"title":"聊在东京奥运会闭幕式这天-二","url":"/2021/08/19/聊在东京奥运会闭幕式这天-二/"},{"title":"聊一下 SpringBoot 中动态切换数据源的方法","url":"/2021/09/26/聊一下-SpringBoot-中动态切换数据源的方法/"},{"title":"聊在东京奥运会闭幕式这天","url":"/2021/08/08/聊在东京奥运会闭幕式这天/"},{"title":"聊聊 Dubbo 的 SPI 续之自适应拓展","url":"/2020/06/06/聊聊-Dubbo-的-SPI-续之自适应拓展/"},{"title":"聊一下 SpringBoot 设置非 web 应用的方法","url":"/2022/07/31/聊一下-SpringBoot-设置非-web-应用的方法/"},{"title":"聊聊 Dubbo 的 SPI","url":"/2020/05/31/聊聊-Dubbo-的-SPI/"},{"title":"聊聊 Java 中绕不开的 Synchronized 关键字-二","url":"/2021/06/27/聊聊-Java-中绕不开的-Synchronized-关键字-二/"},{"title":"聊聊 Dubbo 的容错机制","url":"/2020/11/22/聊聊-Dubbo-的容错机制/"},{"title":"聊聊 Java 的 equals 和 hashCode 方法","url":"/2021/01/03/聊聊-Java-的-equals-和-hashCode-方法/"},{"title":"聊聊 Java 中绕不开的 Synchronized 关键字","url":"/2021/06/20/聊聊-Java-中绕不开的-Synchronized-关键字/"},{"title":"聊聊 Java 的类加载机制一","url":"/2020/11/08/聊聊-Java-的类加载机制/"},{"title":"聊聊 Java 的类加载机制二","url":"/2021/06/13/聊聊-Java-的类加载机制二/"},{"title":"聊聊 Linux 下的 top 命令","url":"/2021/03/28/聊聊-Linux-下的-top-命令/"},{"title":"聊聊 Java 自带的那些*逆天*工具","url":"/2020/08/02/聊聊-Java-自带的那些逆天工具/"},{"title":"聊聊 Sharding-Jdbc 分库分表下的分页方案","url":"/2022/01/09/聊聊-Sharding-Jdbc-分库分表下的分页方案/"},{"title":"聊聊 RocketMQ 的 Broker 源码","url":"/2020/07/19/聊聊-RocketMQ-的-Broker-源码/"},{"title":"聊聊 Sharding-Jdbc 的简单使用","url":"/2021/12/12/聊聊-Sharding-Jdbc-的简单使用/"},{"title":"聊聊 Sharding-Jdbc 的简单原理初篇","url":"/2021/12/26/聊聊-Sharding-Jdbc-的简单原理初篇/"},{"title":"聊聊 dubbo 的线程池","url":"/2021/04/04/聊聊-dubbo-的线程池/"},{"title":"聊聊 mysql 的 MVCC 续篇","url":"/2020/05/02/聊聊-mysql-的-MVCC-续篇/"},{"title":"聊聊 mysql 的 MVCC 续续篇之锁分析","url":"/2020/05/10/聊聊-mysql-的-MVCC-续续篇之加锁分析/"},{"title":"聊聊 mysql 的 MVCC","url":"/2020/04/26/聊聊-mysql-的-MVCC/"},{"title":"聊聊 mysql 索引的一些细节","url":"/2020/12/27/聊聊-mysql-索引的一些细节/"},{"title":"聊聊 redis 缓存的应用问题","url":"/2021/01/31/聊聊-redis-缓存的应用问题/"},{"title":"聊聊Java中的单例模式","url":"/2019/12/21/聊聊Java中的单例模式/"},{"title":"聊聊一次 brew update 引发的血案","url":"/2020/06/13/聊聊一次-brew-update-引发的血案/"},{"title":"聊聊 SpringBoot 自动装配","url":"/2021/07/11/聊聊SpringBoot-自动装配/"},{"title":"聊聊厦门旅游的好与不好","url":"/2021/04/11/聊聊厦门旅游的好与不好/"},{"title":"聊聊传说中的 ThreadLocal","url":"/2021/05/30/聊聊传说中的-ThreadLocal/"},{"title":"聊聊如何识别和意识到日常生活中的各类危险","url":"/2021/06/06/聊聊如何识别和意识到日常生活中的各类危险/"},{"title":"聊聊我刚学会的应用诊断方法","url":"/2020/05/22/聊聊我刚学会的应用诊断方法/"},{"title":"聊聊我理解的分布式事务","url":"/2020/05/17/聊聊我理解的分布式事务/"},{"title":"聊聊最近平淡的生活之又聊通勤","url":"/2021/11/07/聊聊最近平淡的生活/"},{"title":"聊聊最近平淡的生活之看看老剧","url":"/2021/11/21/聊聊最近平淡的生活之看看老剧/"},{"title":"聊聊最近平淡的生活之看《神探狄仁杰》","url":"/2021/12/19/聊聊最近平淡的生活之看《神探狄仁杰》/"},{"title":"聊聊给亲戚朋友的老电脑重装系统那些事儿","url":"/2021/05/09/聊聊给亲戚朋友的老电脑重装系统那些事儿/"},{"title":"聊聊我的远程工作体验","url":"/2022/06/26/聊聊我的远程工作体验/"},{"title":"聊聊那些加塞狗","url":"/2021/01/17/聊聊那些加塞狗/"},{"title":"聊聊部分公交车的设计bug","url":"/2021/12/05/聊聊部分公交车的设计bug/"},{"title":"聊聊最近平淡的生活之《花束般的恋爱》观后感","url":"/2021/12/31/聊聊最近平淡的生活之《花束般的恋爱》观后感/"},{"title":"这周末我又在老丈人家打了天小工","url":"/2020/08/30/这周末我又在老丈人家打了天小工/"},{"title":"重看了下《蛮荒记》说说感受","url":"/2021/10/10/重看了下《蛮荒记》说说感受/"},{"title":"闲聊下乘公交的用户体验","url":"/2021/02/28/闲聊下乘公交的用户体验/"},{"title":"聊聊这次换车牌及其他","url":"/2022/02/20/聊聊这次换车牌及其他/"},{"title":"闲话篇-也算碰到了为老不尊和坏人变老了的典型案例","url":"/2022/05/22/闲话篇-也算碰到了为老不尊和坏人变老了的典型案例/"},{"title":"记录下 Java Stream 的一些高效操作","url":"/2022/05/15/记录下-Java-Lambda-的一些高效操作/"},{"title":"记录下 zookeeper 集群迁移和易错点","url":"/2022/05/29/记录下-zookeeper-集群迁移/"},{"title":"难得的大扫除","url":"/2022/04/10/难得的大扫除/"},{"title":"闲话篇-路遇神逻辑骑车带娃爹","url":"/2022/05/08/闲话篇-路遇神逻辑骑车带娃爹/"}] \ No newline at end of file +[{"title":"村上春树《1Q84》读后感","url":"/2019/12/18/1Q84读后感/"},{"title":"2019年终总结","url":"/2020/02/01/2019年终总结/"},{"title":"2020 年终总结","url":"/2021/03/31/2020-年终总结/"},{"title":"2020年中总结","url":"/2020/07/11/2020年中总结/"},{"title":"2021 年中总结","url":"/2021/07/18/2021-年中总结/"},{"title":"AQS篇一","url":"/2021/02/14/AQS篇一/"},{"title":"2021 年终总结","url":"/2022/01/22/2021-年终总结/"},{"title":"AQS篇二 之 Condition 浅析笔记","url":"/2021/02/21/AQS-之-Condition-浅析笔记/"},{"title":"34_Search_for_a_Range","url":"/2016/08/14/34-Search-for-a-Range/"},{"title":"AbstractQueuedSynchronizer","url":"/2019/09/23/AbstractQueuedSynchronizer/"},{"title":"add-two-number","url":"/2015/04/14/Add-Two-Number/"},{"title":"Apollo 的 value 注解是怎么自动更新的","url":"/2020/11/01/Apollo-的-value-注解是怎么自动更新的/"},{"title":"Comparator使用小记","url":"/2020/04/05/Comparator使用小记/"},{"title":"Clone Graph Part I","url":"/2014/12/30/Clone-Graph-Part-I/"},{"title":"Disruptor 系列一","url":"/2022/02/13/Disruptor-系列一/"},{"title":"Disruptor 系列二","url":"/2022/02/27/Disruptor-系列二/"},{"title":"Dubbo 使用的几个记忆点","url":"/2022/04/02/Dubbo-使用的几个记忆点/"},{"title":"Filter, Interceptor, Aop, 啥, 啥, 啥? 这些都是啥?","url":"/2020/08/22/Filter-Intercepter-Aop-啥-啥-啥-这些都是啥/"},{"title":"JVM源码分析之G1垃圾收集器分析一","url":"/2019/12/07/JVM-G1-Part-1/"},{"title":"G1收集器概述","url":"/2020/02/09/G1收集器概述/"},{"title":"Leetcode 028 实现 strStr() ( Implement strStr() ) 题解分析","url":"/2021/10/31/Leetcode-028-实现-strStr-Implement-strStr-题解分析/"},{"title":"Leetcode 021 合并两个有序链表 ( Merge Two Sorted Lists ) 题解分析","url":"/2021/10/07/Leetcode-021-合并两个有序链表-Merge-Two-Sorted-Lists-题解分析/"},{"title":"Leetcode 053 最大子序和 ( Maximum Subarray ) 题解分析","url":"/2021/11/28/Leetcode-053-最大子序和-Maximum-Subarray-题解分析/"},{"title":"Leetcode 104 二叉树的最大深度(Maximum Depth of Binary Tree) 题解分析","url":"/2020/10/25/Leetcode-104-二叉树的最大深度-Maximum-Depth-of-Binary-Tree-题解分析/"},{"title":"Leetcode 105 从前序与中序遍历序列构造二叉树(Construct Binary Tree from Preorder and Inorder Traversal) 题解分析","url":"/2020/12/13/Leetcode-105-从前序与中序遍历序列构造二叉树-Construct-Binary-Tree-from-Preorder-and-Inorder-Traversal-题解分析/"},{"title":"Leetcode 1115 交替打印 FooBar ( Print FooBar Alternately *Medium* ) 题解分析","url":"/2022/05/01/Leetcode-1115-交替打印-FooBar-Print-FooBar-Alternately-Medium-题解分析/"},{"title":"Leetcode 121 买卖股票的最佳时机(Best Time to Buy and Sell Stock) 题解分析","url":"/2021/03/14/Leetcode-121-买卖股票的最佳时机-Best-Time-to-Buy-and-Sell-Stock-题解分析/"},{"title":"Leetcode 124 二叉树中的最大路径和(Binary Tree Maximum Path Sum) 题解分析","url":"/2021/01/24/Leetcode-124-二叉树中的最大路径和-Binary-Tree-Maximum-Path-Sum-题解分析/"},{"title":"Leetcode 1260 二维网格迁移 ( Shift 2D Grid *Easy* ) 题解分析","url":"/2022/07/22/Leetcode-1260-二维网格迁移-Shift-2D-Grid-Easy-题解分析/"},{"title":"Leetcode 155 最小栈(Min Stack) 题解分析","url":"/2020/12/06/Leetcode-155-最小栈-Min-Stack-题解分析/"},{"title":"Leetcode 16 最接近的三数之和 ( 3Sum Closest *Medium* ) 题解分析","url":"/2022/08/06/Leetcode-16-最接近的三数之和-3Sum-Closest-Medium-题解分析/"},{"title":"Leetcode 160 相交链表(intersection-of-two-linked-lists) 题解分析","url":"/2021/01/10/Leetcode-160-相交链表-intersection-of-two-linked-lists-题解分析/"},{"title":"Leetcode 20 有效的括号 ( Valid Parentheses *Easy* ) 题解分析","url":"/2022/07/02/Leetcode-20-有效的括号-Valid-Parentheses-Easy-题解分析/"},{"title":"Leetcode 234 回文链表(Palindrome Linked List) 题解分析","url":"/2020/11/15/Leetcode-234-回文联表-Palindrome-Linked-List-题解分析/"},{"title":"Leetcode 2 Add Two Numbers 题解分析","url":"/2020/10/11/Leetcode-2-Add-Two-Numbers-题解分析/"},{"title":"Leetcode 236 二叉树的最近公共祖先(Lowest Common Ancestor of a Binary Tree) 题解分析","url":"/2021/05/23/Leetcode-236-二叉树的最近公共祖先-Lowest-Common-Ancestor-of-a-Binary-Tree-题解分析/"},{"title":"Leetcode 3 Longest Substring Without Repeating Characters 题解分析","url":"/2020/09/20/Leetcode-3-Longest-Substring-Without-Repeating-Characters-题解分析/"},{"title":"Leetcode 278 第一个错误的版本 ( First Bad Version *Easy* ) 题解分析","url":"/2022/08/14/Leetcode-278-第一个错误的版本-First-Bad-Version-Easy-题解分析/"},{"title":"Leetcode 349 两个数组的交集 ( Intersection of Two Arrays *Easy* ) 题解分析","url":"/2022/03/07/Leetcode-349-两个数组的交集-Intersection-of-Two-Arrays-Easy-题解分析/"},{"title":"Leetcode 4 寻找两个正序数组的中位数 ( Median of Two Sorted Arrays *Hard* ) 题解分析","url":"/2022/03/27/Leetcode-4-寻找两个正序数组的中位数-Median-of-Two-Sorted-Arrays-Hard-题解分析/"},{"title":"Leetcode 48 旋转图像(Rotate Image) 题解分析","url":"/2021/05/01/Leetcode-48-旋转图像-Rotate-Image-题解分析/"},{"title":"Leetcode 42 接雨水 (Trapping Rain Water) 题解分析","url":"/2021/07/04/Leetcode-42-接雨水-Trapping-Rain-Water-题解分析/"},{"title":"Leetcode 698 划分为k个相等的子集 ( Partition to K Equal Sum Subsets *Medium* ) 题解分析","url":"/2022/06/19/Leetcode-698-划分为k个相等的子集-Partition-to-K-Equal-Sum-Subsets-Medium-题解分析/"},{"title":"Leetcode 885 螺旋矩阵 III ( Spiral Matrix III *Medium* ) 题解分析","url":"/2022/08/23/Leetcode-885-螺旋矩阵-III-Spiral-Matrix-III-Medium-题解分析/"},{"title":"Leetcode 83 删除排序链表中的重复元素 ( Remove Duplicates from Sorted List *Easy* ) 题解分析","url":"/2022/03/13/Leetcode-83-删除排序链表中的重复元素-Remove-Duplicates-from-Sorted-List-Easy-题解分析/"},{"title":"Linux 下 grep 命令的一点小技巧","url":"/2020/08/06/Linux-下-grep-命令的一点小技巧/"},{"title":"leetcode no.3","url":"/2015/04/15/Leetcode-No-3/"},{"title":"Maven实用小技巧","url":"/2020/02/16/Maven实用小技巧/"},{"title":"Number of 1 Bits","url":"/2015/03/11/Number-Of-1-Bits/"},{"title":"Path Sum","url":"/2015/01/04/Path-Sum/"},{"title":"Redis_分布式锁","url":"/2019/12/10/Redis-Part-1/"},{"title":"Reverse Bits","url":"/2015/03/11/Reverse-Bits/"},{"title":"Reverse Integer","url":"/2015/03/13/Reverse-Integer/"},{"title":"two sum","url":"/2015/01/14/Two-Sum/"},{"title":"ambari-summary","url":"/2017/05/09/ambari-summary/"},{"title":"MFC 模态对话框","url":"/2014/12/24/MFC 模态对话框/"},{"title":"binary-watch","url":"/2016/09/29/binary-watch/"},{"title":"docker比一般多一点的初学者介绍","url":"/2020/03/08/docker比一般多一点的初学者介绍/"},{"title":"docker比一般多一点的初学者介绍三","url":"/2020/03/21/docker比一般多一点的初学者介绍三/"},{"title":"docker比一般多一点的初学者介绍二","url":"/2020/03/15/docker比一般多一点的初学者介绍二/"},{"title":"dubbo 客户端配置的一个重要知识点","url":"/2022/06/11/dubbo-客户端配置的一个重要知识点/"},{"title":"docker-mysql-cluster","url":"/2016/08/14/docker-mysql-cluster/"},{"title":"docker使用中发现的echo命令的一个小技巧及其他","url":"/2020/03/29/echo命令的一个小技巧/"},{"title":"gogs使用webhook部署react单页应用","url":"/2020/02/22/gogs使用webhook部署react单页应用/"},{"title":"invert-binary-tree","url":"/2015/06/22/invert-binary-tree/"},{"title":"minimum-size-subarray-sum-209","url":"/2016/10/11/minimum-size-subarray-sum-209/"},{"title":"C++ 指针使用中的一个小问题","url":"/2014/12/23/my-new-post/"},{"title":"mybatis 的 foreach 使用的注意点","url":"/2022/07/09/mybatis-的-foreach-使用的注意点/"},{"title":"mybatis 的 $ 和 # 是有啥区别","url":"/2020/09/06/mybatis-的-和-是有啥区别/"},{"title":"mybatis 的缓存是怎么回事","url":"/2020/10/03/mybatis-的缓存是怎么回事/"},{"title":"openresty","url":"/2019/06/18/openresty/"},{"title":"pcre-intro-and-a-simple-package","url":"/2015/01/16/pcre-intro-and-a-simple-package/"},{"title":"php-abstract-class-and-interface","url":"/2016/11/10/php-abstract-class-and-interface/"},{"title":"rabbitmq-tips","url":"/2017/04/25/rabbitmq-tips/"},{"title":"redis 的 rdb 和 COW 介绍","url":"/2021/08/15/redis-的-rdb-和-COW-介绍/"},{"title":"nginx 日志小记","url":"/2022/04/17/nginx-日志小记/"},{"title":"redis数据结构介绍-第一部分 SDS,链表,字典","url":"/2019/12/26/redis数据结构介绍/"},{"title":"redis数据结构介绍三-第三部分 整数集合","url":"/2020/01/10/redis数据结构介绍三/"},{"title":"redis数据结构介绍二-第二部分 跳表","url":"/2020/01/04/redis数据结构介绍二/"},{"title":"redis数据结构介绍五-第五部分 对象","url":"/2020/01/20/redis数据结构介绍五/"},{"title":"redis数据结构介绍六 快表","url":"/2020/01/22/redis数据结构介绍六/"},{"title":"redis数据结构介绍四-第四部分 压缩表","url":"/2020/01/19/redis数据结构介绍四/"},{"title":"redis淘汰策略复习","url":"/2021/08/01/redis淘汰策略复习/"},{"title":"redis系列介绍七-过期策略","url":"/2020/04/12/redis系列介绍七/"},{"title":"redis系列介绍八-淘汰策略","url":"/2020/04/18/redis系列介绍八/"},{"title":"redis过期策略复习","url":"/2021/07/25/redis过期策略复习/"},{"title":"rust学习笔记-所有权三之切片","url":"/2021/05/16/rust学习笔记-所有权三之切片/"},{"title":"rust学习笔记-所有权二","url":"/2021/04/18/rust学习笔记-所有权二/"},{"title":"rust学习笔记-所有权一","url":"/2021/04/18/rust学习笔记/"},{"title":"spark-little-tips","url":"/2017/03/28/spark-little-tips/"},{"title":"spring event 介绍","url":"/2022/01/30/spring-event-介绍/"},{"title":"summary-ranges-228","url":"/2016/10/12/summary-ranges-228/"},{"title":"swoole-websocket-test","url":"/2016/07/13/swoole-websocket-test/"},{"title":"wordpress 忘记密码的一种解决方法","url":"/2021/12/05/wordpress-忘记密码的一种解决方法/"},{"title":"《垃圾回收算法手册读书》笔记之整理算法","url":"/2021/03/07/《垃圾回收算法手册读书》笔记之整理算法/"},{"title":"《长安的荔枝》读后感","url":"/2022/07/17/《长安的荔枝》读后感/"},{"title":"上次的其他 外行聊国足","url":"/2022/03/06/上次的其他-外行聊国足/"},{"title":"一个 nginx 的简单记忆点","url":"/2022/08/21/一个-nginx-的简单记忆点/"},{"title":"介绍一下 RocketMQ","url":"/2020/06/21/介绍一下-RocketMQ/"},{"title":"介绍下最近比较实用的端口转发","url":"/2021/11/14/介绍下最近比较实用的端口转发/"},{"title":"从丁仲礼被美国制裁聊点啥","url":"/2020/12/20/从丁仲礼被美国制裁聊点啥/"},{"title":"从清华美院学姐聊聊我们身边的恶人","url":"/2020/11/29/从清华美院学姐聊聊我们身边的恶人/"},{"title":"关于公共交通再吐个槽","url":"/2021/03/21/关于公共交通再吐个槽/"},{"title":"分享记录一下一个 git 操作方法","url":"/2022/02/06/分享记录一下一个-git-操作方法/"},{"title":"分享记录一下一个 scp 操作方法","url":"/2022/02/06/分享记录一下一个-scp-操作方法/"},{"title":"在老丈人家的小工记三","url":"/2020/09/13/在老丈人家的小工记三/"},{"title":"周末我在老丈人家打了天小工","url":"/2020/08/16/周末我在老丈人家打了天小工/"},{"title":"在老丈人家的小工记五","url":"/2020/10/18/在老丈人家的小工记五/"},{"title":"在老丈人家的小工记四","url":"/2020/09/26/在老丈人家的小工记四/"},{"title":"屯菜惊魂记","url":"/2022/04/24/屯菜惊魂记/"},{"title":"我是如何走上跑步这条不归路的","url":"/2020/07/26/我是如何走上跑步这条不归路的/"},{"title":"搬运两个 StackOverflow 上的 Mysql 编码相关的问题解答","url":"/2022/01/16/搬运两个-StackOverflow-上的-Mysql-编码相关的问题解答/"},{"title":"是何原因竟让两人深夜奔袭十公里","url":"/2022/06/05/是何原因竟让两人深夜奔袭十公里/"},{"title":"看完了扫黑风暴,聊聊感想","url":"/2021/10/24/看完了扫黑风暴-聊聊感想/"},{"title":"给小电驴上牌","url":"/2022/03/20/给小电驴上牌/"},{"title":"聊一下 RocketMQ 的 DefaultMQPushConsumer 源码","url":"/2020/06/26/聊一下-RocketMQ-的-Consumer/"},{"title":"聊一下 RocketMQ 的 NameServer 源码","url":"/2020/07/05/聊一下-RocketMQ-的-NameServer-源码/"},{"title":"聊一下 RocketMQ 的消息存储之 MMAP","url":"/2021/09/04/聊一下-RocketMQ-的消息存储/"},{"title":"聊一下 RocketMQ 的消息存储三","url":"/2021/10/03/聊一下-RocketMQ-的消息存储三/"},{"title":"聊一下 RocketMQ 的消息存储二","url":"/2021/09/12/聊一下-RocketMQ-的消息存储二/"},{"title":"聊一下 RocketMQ 的消息存储四","url":"/2021/10/17/聊一下-RocketMQ-的消息存储四/"},{"title":"聊一下 RocketMQ 的顺序消息","url":"/2021/08/29/聊一下-RocketMQ-的顺序消息/"},{"title":"聊一下 SpringBoot 中使用的 cglib 作为动态代理中的一个注意点","url":"/2021/09/19/聊一下-SpringBoot-中使用的-cglib-作为动态代理中的一个注意点/"},{"title":"聊一下 SpringBoot 中动态切换数据源的方法","url":"/2021/09/26/聊一下-SpringBoot-中动态切换数据源的方法/"},{"title":"聊一下 SpringBoot 设置非 web 应用的方法","url":"/2022/07/31/聊一下-SpringBoot-设置非-web-应用的方法/"},{"title":"聊在东京奥运会闭幕式这天-二","url":"/2021/08/19/聊在东京奥运会闭幕式这天-二/"},{"title":"聊在东京奥运会闭幕式这天","url":"/2021/08/08/聊在东京奥运会闭幕式这天/"},{"title":"聊聊 Dubbo 的 SPI 续之自适应拓展","url":"/2020/06/06/聊聊-Dubbo-的-SPI-续之自适应拓展/"},{"title":"聊聊 Dubbo 的 SPI","url":"/2020/05/31/聊聊-Dubbo-的-SPI/"},{"title":"聊聊 Dubbo 的容错机制","url":"/2020/11/22/聊聊-Dubbo-的容错机制/"},{"title":"聊聊 Java 中绕不开的 Synchronized 关键字-二","url":"/2021/06/27/聊聊-Java-中绕不开的-Synchronized-关键字-二/"},{"title":"聊聊 Java 中绕不开的 Synchronized 关键字","url":"/2021/06/20/聊聊-Java-中绕不开的-Synchronized-关键字/"},{"title":"聊聊 Java 的 equals 和 hashCode 方法","url":"/2021/01/03/聊聊-Java-的-equals-和-hashCode-方法/"},{"title":"聊聊 Java 的类加载机制一","url":"/2020/11/08/聊聊-Java-的类加载机制/"},{"title":"聊聊 Java 自带的那些*逆天*工具","url":"/2020/08/02/聊聊-Java-自带的那些逆天工具/"},{"title":"聊聊 Java 的类加载机制二","url":"/2021/06/13/聊聊-Java-的类加载机制二/"},{"title":"聊聊 Linux 下的 top 命令","url":"/2021/03/28/聊聊-Linux-下的-top-命令/"},{"title":"聊聊 RocketMQ 的 Broker 源码","url":"/2020/07/19/聊聊-RocketMQ-的-Broker-源码/"},{"title":"聊聊 Sharding-Jdbc 分库分表下的分页方案","url":"/2022/01/09/聊聊-Sharding-Jdbc-分库分表下的分页方案/"},{"title":"聊聊 Sharding-Jdbc 的简单使用","url":"/2021/12/12/聊聊-Sharding-Jdbc-的简单使用/"},{"title":"寄生虫观后感","url":"/2020/03/01/寄生虫观后感/"},{"title":"聊聊 Sharding-Jdbc 的简单原理初篇","url":"/2021/12/26/聊聊-Sharding-Jdbc-的简单原理初篇/"},{"title":"聊聊 dubbo 的线程池","url":"/2021/04/04/聊聊-dubbo-的线程池/"},{"title":"聊聊 mysql 的 MVCC 续篇","url":"/2020/05/02/聊聊-mysql-的-MVCC-续篇/"},{"title":"聊聊 mysql 的 MVCC 续续篇之锁分析","url":"/2020/05/10/聊聊-mysql-的-MVCC-续续篇之加锁分析/"},{"title":"聊聊 mysql 的 MVCC","url":"/2020/04/26/聊聊-mysql-的-MVCC/"},{"title":"聊聊 mysql 索引的一些细节","url":"/2020/12/27/聊聊-mysql-索引的一些细节/"},{"title":"聊聊 redis 缓存的应用问题","url":"/2021/01/31/聊聊-redis-缓存的应用问题/"},{"title":"聊聊Java中的单例模式","url":"/2019/12/21/聊聊Java中的单例模式/"},{"title":"关于读书打卡与分享","url":"/2021/02/07/关于读书打卡与分享/"},{"title":"聊聊 SpringBoot 自动装配","url":"/2021/07/11/聊聊SpringBoot-自动装配/"},{"title":"聊聊一次 brew update 引发的血案","url":"/2020/06/13/聊聊一次-brew-update-引发的血案/"},{"title":"聊聊传说中的 ThreadLocal","url":"/2021/05/30/聊聊传说中的-ThreadLocal/"},{"title":"聊聊厦门旅游的好与不好","url":"/2021/04/11/聊聊厦门旅游的好与不好/"},{"title":"聊聊如何识别和意识到日常生活中的各类危险","url":"/2021/06/06/聊聊如何识别和意识到日常生活中的各类危险/"},{"title":"聊聊我刚学会的应用诊断方法","url":"/2020/05/22/聊聊我刚学会的应用诊断方法/"},{"title":"聊聊我理解的分布式事务","url":"/2020/05/17/聊聊我理解的分布式事务/"},{"title":"聊聊我的远程工作体验","url":"/2022/06/26/聊聊我的远程工作体验/"},{"title":"聊聊最近平淡的生活之又聊通勤","url":"/2021/11/07/聊聊最近平淡的生活/"},{"title":"聊聊最近平淡的生活之《花束般的恋爱》观后感","url":"/2021/12/31/聊聊最近平淡的生活之《花束般的恋爱》观后感/"},{"title":"聊聊最近平淡的生活之看《神探狄仁杰》","url":"/2021/12/19/聊聊最近平淡的生活之看《神探狄仁杰》/"},{"title":"聊聊最近平淡的生活之看看老剧","url":"/2021/11/21/聊聊最近平淡的生活之看看老剧/"},{"title":"聊聊给亲戚朋友的老电脑重装系统那些事儿","url":"/2021/05/09/聊聊给亲戚朋友的老电脑重装系统那些事儿/"},{"title":"聊聊这次换车牌及其他","url":"/2022/02/20/聊聊这次换车牌及其他/"},{"title":"聊聊那些加塞狗","url":"/2021/01/17/聊聊那些加塞狗/"},{"title":"聊聊部分公交车的设计bug","url":"/2021/12/05/聊聊部分公交车的设计bug/"},{"title":"记录下 Java Stream 的一些高效操作","url":"/2022/05/15/记录下-Java-Lambda-的一些高效操作/"},{"title":"记录下 zookeeper 集群迁移和易错点","url":"/2022/05/29/记录下-zookeeper-集群迁移/"},{"title":"这周末我又在老丈人家打了天小工","url":"/2020/08/30/这周末我又在老丈人家打了天小工/"},{"title":"重看了下《蛮荒记》说说感受","url":"/2021/10/10/重看了下《蛮荒记》说说感受/"},{"title":"闲聊下乘公交的用户体验","url":"/2021/02/28/闲聊下乘公交的用户体验/"},{"title":"闲话篇-也算碰到了为老不尊和坏人变老了的典型案例","url":"/2022/05/22/闲话篇-也算碰到了为老不尊和坏人变老了的典型案例/"},{"title":"闲话篇-路遇神逻辑骑车带娃爹","url":"/2022/05/08/闲话篇-路遇神逻辑骑车带娃爹/"},{"title":"难得的大扫除","url":"/2022/04/10/难得的大扫除/"}] \ No newline at end of file diff --git a/page/21/index.html b/page/21/index.html index 6f522bfd2e..2e1d91a42a 100644 --- a/page/21/index.html +++ b/page/21/index.html @@ -115,7 +115,7 @@ Output: [8,9,9,9,0,0,0,1] // tail = null; return root; - }

这里唯二需要注意的就是两个点,一个是循环条件需要包含进位值还存在的情况,还有一个是最后一个节点,如果是空的了,就不要在 new 一个出来了,写的比较挫

Java 真的是任何一个中间件,比较常用的那种,都有很多内容值得深挖,比如这个缓存,慢慢有过一些感悟,比如如何提升性能,缓存无疑是一大重要手段,最底层开始 CPU 就有缓存,而且又小又贵,再往上一点内存一般作为硬盘存储在运行时的存储,一般在代码里也会用内存作为一些本地缓存,譬如数据库,像 mysql 这种也是有innodb_buffer_pool来提升查询效率,本质上理解就是用更快的存储作为相对慢存储的缓存,减少查询直接访问较慢的存储,并且这个都是相对的,比起 cpu 的缓存,那内存也是渣,但是与普通机械硬盘相比,那也是两个次元的水平。

闲扯这么多来说说 mybatis 的缓存,mybatis 一般作为一个轻量级的 orm 使用,相对应的就是比较重量级的 hibernate,不过不在这次讨论范围,上一次是主要讲了 mybatis 在解析 sql 过程中,对于两种占位符的不同替换实现策略,这次主要聊下 mybatis 的缓存,前面其实得了解下前置的东西,比如 sqlsession,先当做我们知道 sqlsession 是个什么玩意,可能或多或少的知道 mybatis 是有两级缓存,

一级缓存

第一级的缓存是在 BaseExecutor 中的 PerpetualCache,它是个最基本的缓存实现类,使用了 HashMap 实现缓存功能,代码其实没几十行

public class PerpetualCache implements Cache {
+    }

这里唯二需要注意的就是两个点,一个是循环条件需要包含进位值还存在的情况,还有一个是最后一个节点,如果是空的了,就不要在 new 一个出来了,写的比较挫

Java 真的是任何一个中间件,比较常用的那种,都有很多内容值得深挖,比如这个缓存,慢慢有过一些感悟,比如如何提升性能,缓存无疑是一大重要手段,最底层开始 CPU 就有缓存,而且又小又贵,再往上一点内存一般作为硬盘存储在运行时的存储,一般在代码里也会用内存作为一些本地缓存,譬如数据库,像 mysql 这种也是有innodb_buffer_pool来提升查询效率,本质上理解就是用更快的存储作为相对慢存储的缓存,减少查询直接访问较慢的存储,并且这个都是相对的,比起 cpu 的缓存,那内存也是渣,但是与普通机械硬盘相比,那也是两个次元的水平。

闲扯这么多来说说 mybatis 的缓存,mybatis 一般作为一个轻量级的 orm 使用,相对应的就是比较重量级的 hibernate,不过不在这次讨论范围,上一次是主要讲了 mybatis 在解析 sql 过程中,对于两种占位符的不同替换实现策略,这次主要聊下 mybatis 的缓存,前面其实得了解下前置的东西,比如 sqlsession,先当做我们知道 sqlsession 是个什么玩意,可能或多或少的知道 mybatis 是有两级缓存,

一级缓存

第一级的缓存是在 BaseExecutor 中的 PerpetualCache,它是个最基本的缓存实现类,使用了 HashMap 实现缓存功能,代码其实没几十行

public class PerpetualCache implements Cache {
 
   private final String id;
 
diff --git a/page/22/index.html b/page/22/index.html
index f2e21fa66d..489587c174 100644
--- a/page/22/index.html
+++ b/page/22/index.html
@@ -38,7 +38,7 @@ Output: 0

注释应该写的比较清楚了。

小工记三

前面这两周周末也都去老丈人家帮忙了,上上周周六先是去了那个在装修的旧房子那,把三楼收拾了下,因为要搬进来住,来不及等二楼装修好,就要把三楼里的东西都整理干净,这个活感觉是比较 easy,原来是就准备把三楼当放东西仓储的地方了,我们乡下大部分三层楼都是这么用的,这次也是没办法,之前搬进来的木头什么的都搬出去,主要是这上面灰尘太多,后面清理鼻孔的时候都是黑色的了,把东西都搬出去以后主要是地还是很脏,就扫了地拖了地,因为是水泥地,灰尘又太多了,拖起来都是会灰尘扬起来,整个脱完了的确干净很多,然而这会就出了个大乌龙,我们清理的是三楼的西边一间,结果老丈人上来说要住东边那间的🤦‍♂️,不过其实西边的也得清理,因为还是要放被子什么的,不算是白费功夫,接着清理东边那间,之前这个房子做过群租房,里面有个高低铺的床,当时觉得可以用在放被子什么的就没扔,只是拆掉了放旁边,我们就把它擦干净了又装好,发现螺丝🔩少了几个,亘古不变的真理,拆了以后装要不就多几个要不就少几个,不是很牢靠,不过用来放放被子省得放地上总还是可以的,对了前面还做了个事情就是铺地毯,其实也不是地毯,就是类似于墙布雨篷布那种,别人不用了送给我们的,三楼水泥地也不会铺瓷砖地板了就放一下,干净好看点,不过大小不合适要裁一下,那把剪刀是真的太难用了,我手都要抽筋了,它就是刀口只有一小个点是能剪下来的,其他都是钝的,后来还是用刀片直接裁,铺好以后,真的感觉也不太一样了,焕然一新的感觉
差不多中午了就去吃饭了,之前两次是去了一家小饭店,还是还比较干净,但是店里菜不好吃,还死贵,这次去了一家小快餐店,口味好,便宜,味道是真的不错,带鱼跟黄鱼都好吃,一点都不腥,我对这类比较腥的鱼真的是很挑剔的,基本上除了家里做的很少吃外面的,那天抱着试试的态度吃了下,真的还不错,后来丈母娘说好像这家老板是给别人结婚喜事酒席当厨师的,怪不得做的好吃,其实本来是有一点小抗拒,怕不干净什么的,后来发现菜很好吃,而且可能是老丈人跟干活的师傅去吃的比较多,老板很客气,我们吃完饭,还给我们买了葡萄吃,不过这家店有一个槽点,就是饭比较不好吃,有时候会夹生,不过后面聊起来其实是这种小菜馆饭点的通病,烧的太早太多容易多出来浪费,烧的迟了不够吃,而且大的电饭锅比较不容易烧好。
下午前面还是在处理三楼的,窗户上各种钉子,实在是太多了,我后面在走廊上排了一排🤦‍♂️,有些是直接断了,有些是就撬了出来,感觉我在杭州租房也没有这样子各种钉钉子,挂下衣服什么的也不用这么多吧,比较不能理解,搞得到处都是钉子。那天我爸也去帮忙了,主要是在卫生间里做白缝,其实也是个技术活,印象中好像我小时候自己家里也做过这个事情,但是比较模糊了,后面我们三楼搞完了就去帮我爸了,前面是我老婆二爹在那先刷上白缝,这里叫白缝,有些考究的也叫美缝,就是瓷砖铺完之后的缝,如果不去弄的话,里面水泥的颜色就露出来了,而且容易渗水,所以就要用白水泥加胶水搅拌之后糊在缝上,但是也不是直接糊,先要把缝抠一抠,因为铺瓷砖的还不会仔细到每个缝里的水泥都是一样满,而且也需要一些空间糊上去,不然就太表面的一层很容易被水直接冲掉了,然后这次其实也不是用的白水泥,而是直接现成买来就已经配好的用来填缝的,兑水搅拌均匀就好了,后面就主要是我跟我爸在搞,那个时候真的觉得我实在是太胖了,蹲下去真的没一会就受不了了,膝盖什么的太难受了,后面我跪着刷,然后膝盖又疼,也是比较不容易,不过我爸动作很快,我中间跪累了休息一会,我爸就能搞一大片,后面其实我也没做多少(谦虚一下),总体来讲这次不是很累,就是蹲着跪着腿有点受不了,是应该好好减肥了。

这个问题也是面试中常被问到的,就抽空来了解下这个,跳过一大段前面初始化的逻辑,
对于一条select * from t1 where id = #{id}这样的 sql,在初始化扫描 mapper 的xml文件的时候会根据是否是 dynamic 来判断生成 DynamicSqlSource 还是 RawSqlSource,这里它是一条 RawSqlSource,
在这里做了替换,将#{}替换成了?

前面说的是否 dynamic 就是在这里进行判断

// org.apache.ibatis.scripting.xmltags.XMLScriptBuilder#parseScriptNode
+}

注释应该写的比较清楚了。

小工记三

前面这两周周末也都去老丈人家帮忙了,上上周周六先是去了那个在装修的旧房子那,把三楼收拾了下,因为要搬进来住,来不及等二楼装修好,就要把三楼里的东西都整理干净,这个活感觉是比较 easy,原来是就准备把三楼当放东西仓储的地方了,我们乡下大部分三层楼都是这么用的,这次也是没办法,之前搬进来的木头什么的都搬出去,主要是这上面灰尘太多,后面清理鼻孔的时候都是黑色的了,把东西都搬出去以后主要是地还是很脏,就扫了地拖了地,因为是水泥地,灰尘又太多了,拖起来都是会灰尘扬起来,整个脱完了的确干净很多,然而这会就出了个大乌龙,我们清理的是三楼的西边一间,结果老丈人上来说要住东边那间的🤦‍♂️,不过其实西边的也得清理,因为还是要放被子什么的,不算是白费功夫,接着清理东边那间,之前这个房子做过群租房,里面有个高低铺的床,当时觉得可以用在放被子什么的就没扔,只是拆掉了放旁边,我们就把它擦干净了又装好,发现螺丝🔩少了几个,亘古不变的真理,拆了以后装要不就多几个要不就少几个,不是很牢靠,不过用来放放被子省得放地上总还是可以的,对了前面还做了个事情就是铺地毯,其实也不是地毯,就是类似于墙布雨篷布那种,别人不用了送给我们的,三楼水泥地也不会铺瓷砖地板了就放一下,干净好看点,不过大小不合适要裁一下,那把剪刀是真的太难用了,我手都要抽筋了,它就是刀口只有一小个点是能剪下来的,其他都是钝的,后来还是用刀片直接裁,铺好以后,真的感觉也不太一样了,焕然一新的感觉
差不多中午了就去吃饭了,之前两次是去了一家小饭店,还是还比较干净,但是店里菜不好吃,还死贵,这次去了一家小快餐店,口味好,便宜,味道是真的不错,带鱼跟黄鱼都好吃,一点都不腥,我对这类比较腥的鱼真的是很挑剔的,基本上除了家里做的很少吃外面的,那天抱着试试的态度吃了下,真的还不错,后来丈母娘说好像这家老板是给别人结婚喜事酒席当厨师的,怪不得做的好吃,其实本来是有一点小抗拒,怕不干净什么的,后来发现菜很好吃,而且可能是老丈人跟干活的师傅去吃的比较多,老板很客气,我们吃完饭,还给我们买了葡萄吃,不过这家店有一个槽点,就是饭比较不好吃,有时候会夹生,不过后面聊起来其实是这种小菜馆饭点的通病,烧的太早太多容易多出来浪费,烧的迟了不够吃,而且大的电饭锅比较不容易烧好。
下午前面还是在处理三楼的,窗户上各种钉子,实在是太多了,我后面在走廊上排了一排🤦‍♂️,有些是直接断了,有些是就撬了出来,感觉我在杭州租房也没有这样子各种钉钉子,挂下衣服什么的也不用这么多吧,比较不能理解,搞得到处都是钉子。那天我爸也去帮忙了,主要是在卫生间里做白缝,其实也是个技术活,印象中好像我小时候自己家里也做过这个事情,但是比较模糊了,后面我们三楼搞完了就去帮我爸了,前面是我老婆二爹在那先刷上白缝,这里叫白缝,有些考究的也叫美缝,就是瓷砖铺完之后的缝,如果不去弄的话,里面水泥的颜色就露出来了,而且容易渗水,所以就要用白水泥加胶水搅拌之后糊在缝上,但是也不是直接糊,先要把缝抠一抠,因为铺瓷砖的还不会仔细到每个缝里的水泥都是一样满,而且也需要一些空间糊上去,不然就太表面的一层很容易被水直接冲掉了,然后这次其实也不是用的白水泥,而是直接现成买来就已经配好的用来填缝的,兑水搅拌均匀就好了,后面就主要是我跟我爸在搞,那个时候真的觉得我实在是太胖了,蹲下去真的没一会就受不了了,膝盖什么的太难受了,后面我跪着刷,然后膝盖又疼,也是比较不容易,不过我爸动作很快,我中间跪累了休息一会,我爸就能搞一大片,后面其实我也没做多少(谦虚一下),总体来讲这次不是很累,就是蹲着跪着腿有点受不了,是应该好好减肥了。

这个问题也是面试中常被问到的,就抽空来了解下这个,跳过一大段前面初始化的逻辑,
对于一条select * from t1 where id = #{id}这样的 sql,在初始化扫描 mapper 的xml文件的时候会根据是否是 dynamic 来判断生成 DynamicSqlSource 还是 RawSqlSource,这里它是一条 RawSqlSource,
在这里做了替换,将#{}替换成了?

前面说的是否 dynamic 就是在这里进行判断

// org.apache.ibatis.scripting.xmltags.XMLScriptBuilder#parseScriptNode
 public SqlSource parseScriptNode() {
     MixedSqlNode rootSqlNode = parseDynamicTags(context);
     SqlSource sqlSource;
diff --git a/page/23/index.html b/page/23/index.html
index c5945f87ae..1c90c495e8 100644
--- a/page/23/index.html
+++ b/page/23/index.html
@@ -428,7 +428,7 @@
     public String hello() {
         return "hello world";
     }
-}

好了,请求一下,看看 stdout,

搞定完事儿~

这周回家提前约好了要去老丈人家帮下忙,因为在翻修下老房子,活不是特别整的那种,所以大部分都是自己干,或者找个大工临时干几天(我们这那种比较专业的泥工匠叫做大工),像我这样去帮忙的,就是干点小工(把给大工帮忙的,干些偏体力活的叫做小工)的活。从大学毕业以后真的蛮少帮家里干活了,以前上学的时候放假还是帮家里淘个米,简单的扫地拖地啥的,当然刚高考完的时候,还去我爸厂里帮忙干了几天的活,实在是比较累,不过现在想着是觉得自己那时候比较牛,而不是特别排斥这个活,相对于现在的工作来说,导致了一系列的职业病,颈椎腰背都很僵硬,眼镜也不好,还有反流,像我爸那种活反而是脑力加体力的比较好的结合。
这一天的活前半部分主要是在清理厨房,瓷砖上的油污和墙上天花板上即将脱落的石灰或者白色涂料层,这种活特别是瓷砖上的油污,之前在自己家里也干活,还是比较熟悉的,不过前面主要是LD 在干,我主要是先搞墙上和天花板上的,干活还是很需要技巧的,如果直接去铲,那基本我会变成一个灰人,而且吸一鼻子灰,老丈人比较专业,先接上软管用水冲,一冲效果特别好,有些石灰涂料层直接就冲掉了,冲完之后先用带加长杆的刀片铲铲了一圈墙面,说实话因为老房子之前租出去了,所以墙面什么的被糟蹋的比较难看,一层一层的,不过这还算还好,后面主要是天花板上的,这可难倒我了,从小我爸妈是比较把我当小孩管着,爬上爬下的基本都是我爸搞定,但是到了老丈人家也只得硬着头皮上了,爬到跳(一种建筑工地用的架子)上,还有点晃,小心脏扑通扑通跳,而且带加长杆的铲子还是比较重的,铲一会手也有点累,不过坚持着铲完了,上面还是比较平整的,不过下来的时候又把我难住了🤦‍♂️,往下爬的时候有根杆子要跨过去,由于裤子比较紧,强行一把跨过去怕抽筋,所以以一个非常尴尬的姿势停留休息了一会,再跨了过去,幸好事后问 LD,他们都没看到,哈哈哈,然后就是帮忙一起搞瓷砖上的油污,这个太有经验了,不过老丈人更有意思,一会试试啤酒,一会用用沙子,后面在午饭前基本就弄的比较干净了,就坐着等吃饭了,下午午休了会,就继续干活了。
下午是我这次体验的重点了,因为要清理以前贴的墙纸,真的是个很麻烦的活,只能说贴墙纸的师傅活干得太好了,基本不可能整个撕下来,想用铲子一点点铲下来也不行,太轻了就只铲掉表面一层,太重了就把墙纸跟墙面的石灰啥的整个铲下来了,而且手又累又酸,后来想着是不是继续用水冲一下,对着一小面墙试验了下,效果还不错,但是又发现了个问题,那一面墙又有一块是后面糊上去的,铲掉外层的石灰后不平,然后就是最最重头的,也是让我后遗症持续到第二天的,要把那一块糊上去的水泥敲下来,毛估下大概是敲了80%左右,剩下的我的手已经不会用力了,因为那一块应该是要糊上去的始作俑者,就一块里面凹进去的,我拿着榔头敲到我手已经没法使劲了,而且大下午,感觉没五分钟,我的汗已经糊满脸,眼睛也睁不开,不然就流到眼睛里了,此处获得成就一:用榔头敲墙壁,也是个技术加体力的活,而且需要非常好的技巧,否则手马上就废了,敲下去的反作用力,没一会就不行了,然后是看着老丈人兄弟帮忙拆一个柜子,在我看来是个几天都搞不定的活,他轻轻松松在我敲墙的那会就搞定了,以前总觉得我干的活非常有技术含量,可是这个事情真的也是很有技巧啊,它是个把一间房间分隔开的柜子,从底到顶上,还带着门,我还在旁边帮忙撬一下脚踢,一根木条撬半天,唉,成就二:专业的人就是不一样。
最后就是成就三了:我之前沾沾自喜的跑了多少步,做了什么锻炼,其实都是渣渣,像这样干一天活,没经历过的,基本大半天就废了,反过来说,如果能经常去这么干一天活,跑步啥的都是渣渣,消耗的能量远远超过跑个十公里啥的。

这周回家提前约好了要去老丈人家帮下忙,因为在翻修下老房子,活不是特别整的那种,所以大部分都是自己干,或者找个大工临时干几天(我们这那种比较专业的泥工匠叫做大工),像我这样去帮忙的,就是干点小工(把给大工帮忙的,干些偏体力活的叫做小工)的活。从大学毕业以后真的蛮少帮家里干活了,以前上学的时候放假还是帮家里淘个米,简单的扫地拖地啥的,当然刚高考完的时候,还去我爸厂里帮忙干了几天的活,实在是比较累,不过现在想着是觉得自己那时候比较牛,而不是特别排斥这个活,相对于现在的工作来说,导致了一系列的职业病,颈椎腰背都很僵硬,眼镜也不好,还有反流,像我爸那种活反而是脑力加体力的比较好的结合。
这一天的活前半部分主要是在清理厨房,瓷砖上的油污和墙上天花板上即将脱落的石灰或者白色涂料层,这种活特别是瓷砖上的油污,之前在自己家里也干活,还是比较熟悉的,不过前面主要是LD 在干,我主要是先搞墙上和天花板上的,干活还是很需要技巧的,如果直接去铲,那基本我会变成一个灰人,而且吸一鼻子灰,老丈人比较专业,先接上软管用水冲,一冲效果特别好,有些石灰涂料层直接就冲掉了,冲完之后先用带加长杆的刀片铲铲了一圈墙面,说实话因为老房子之前租出去了,所以墙面什么的被糟蹋的比较难看,一层一层的,不过这还算还好,后面主要是天花板上的,这可难倒我了,从小我爸妈是比较把我当小孩管着,爬上爬下的基本都是我爸搞定,但是到了老丈人家也只得硬着头皮上了,爬到跳(一种建筑工地用的架子)上,还有点晃,小心脏扑通扑通跳,而且带加长杆的铲子还是比较重的,铲一会手也有点累,不过坚持着铲完了,上面还是比较平整的,不过下来的时候又把我难住了🤦‍♂️,往下爬的时候有根杆子要跨过去,由于裤子比较紧,强行一把跨过去怕抽筋,所以以一个非常尴尬的姿势停留休息了一会,再跨了过去,幸好事后问 LD,他们都没看到,哈哈哈,然后就是帮忙一起搞瓷砖上的油污,这个太有经验了,不过老丈人更有意思,一会试试啤酒,一会用用沙子,后面在午饭前基本就弄的比较干净了,就坐着等吃饭了,下午午休了会,就继续干活了。
下午是我这次体验的重点了,因为要清理以前贴的墙纸,真的是个很麻烦的活,只能说贴墙纸的师傅活干得太好了,基本不可能整个撕下来,想用铲子一点点铲下来也不行,太轻了就只铲掉表面一层,太重了就把墙纸跟墙面的石灰啥的整个铲下来了,而且手又累又酸,后来想着是不是继续用水冲一下,对着一小面墙试验了下,效果还不错,但是又发现了个问题,那一面墙又有一块是后面糊上去的,铲掉外层的石灰后不平,然后就是最最重头的,也是让我后遗症持续到第二天的,要把那一块糊上去的水泥敲下来,毛估下大概是敲了80%左右,剩下的我的手已经不会用力了,因为那一块应该是要糊上去的始作俑者,就一块里面凹进去的,我拿着榔头敲到我手已经没法使劲了,而且大下午,感觉没五分钟,我的汗已经糊满脸,眼睛也睁不开,不然就流到眼睛里了,此处获得成就一:用榔头敲墙壁,也是个技术加体力的活,而且需要非常好的技巧,否则手马上就废了,敲下去的反作用力,没一会就不行了,然后是看着老丈人兄弟帮忙拆一个柜子,在我看来是个几天都搞不定的活,他轻轻松松在我敲墙的那会就搞定了,以前总觉得我干的活非常有技术含量,可是这个事情真的也是很有技巧啊,它是个把一间房间分隔开的柜子,从底到顶上,还带着门,我还在旁边帮忙撬一下脚踢,一根木条撬半天,唉,成就二:专业的人就是不一样。
最后就是成就三了:我之前沾沾自喜的跑了多少步,做了什么锻炼,其实都是渣渣,像这样干一天活,没经历过的,基本大半天就废了,反过来说,如果能经常去这么干一天活,跑步啥的都是渣渣,消耗的能量远远超过跑个十公里啥的。

用了比较久的 grep 命令,其实都只是用了最最基本的功能来查日志,

譬如


 grep 'xxx' xxxx.log
 

然后有挺多情况比如想要找日志里带一些符号什么的,就需要用到一些特殊的

比如这样\"userId\":\"123456\",因为比如用户 ID 有时候会跟其他的 id 一样,只用具体的值 123456 来查的话干扰信息太多了,如果直接这样


 grep '\"userId\":\"123456\"' xxxx.log
diff --git a/page/26/index.html b/page/26/index.html
index 427dd941d2..d05394224c 100644
--- a/page/26/index.html
+++ b/page/26/index.html
@@ -1,4 +1,4 @@
-Nicksxs's Blog - What hurts more, the pain of hard work or the pain of regret?

Nicksxs's Blog

What hurts more, the pain of hard work or the pain of regret?

0%

看完前面两篇水文之后,感觉不得不来分析下 mysql 的锁了,其实前面说到幻读的时候是有个前提没提到的,比如一个select * from table1 where id = 1这种查询语句其实是不会加传说中的锁的,当然这里是指在 RR 或者 RC 隔离级别下,
看一段 mysql官方文档

SELECT ... FROM is a consistent read, reading a snapshot of the database and setting no locks unless the transaction isolation level is set to SERIALIZABLE. For SERIALIZABLE level, the search sets shared next-key locks on the index records it encounters. However, only an index record lock is required for statements that lock rows using a unique index to search for a unique row.

纯粹的这种一致性读,实际读取的是快照,也就是基于 read view 的读取方式,除非当前隔离级别是SERIALIZABLE
但是对于以下几类

  • select * from table where ? lock in share mode;
  • select * from table where ? for update;
  • insert into table values (...);
  • update table set ? where ?;
  • delete from table where ?;

除了第一条是 S 锁之外,其他都是 X 排他锁,这边在顺带下,S 锁表示共享锁, X 表示独占锁,同为 S 锁之间不冲突,S 与 X,X 与 S,X 与 X 之间都冲突,也就是加了前者,后者就加不上了
我们知道对于 RC 级别会出现幻读现象,对于 RR 级别不会出现,主要的区别是 RR 级别下对于以上的加锁读取都根据情况加上了 gap 锁,那么是不是 RR 级别下以上所有的都是要加 gap 锁呢,当然不是
举个例子,RR 事务隔离级别下,table1 有个主键id 字段
select * from table1 where id = 10 for update
这条语句要加 gap 锁吗?
答案是不需要,这里其实算是我看了这么久的一点自己的理解,啥时候要加 gap 锁,判断的条件是根据我查询的数据是否会因为不加 gap 锁而出现数量的不一致,我上面这条查询语句,在什么情况下会出现查询结果数量不一致呢,只要在这条记录被更新或者删除的时候,有没有可能我第一次查出来一条,第二次变成两条了呢,不可能,因为是主键索引。
再变更下这个题的条件,当 id 不是主键,但是是唯一索引,这样需要怎么加锁,注意问题是怎么加锁,不是需不需要加 gap 锁,这里呢就是稍微延伸一下,把聚簇索引(主键索引)和二级索引带一下,当 id 不是主键,说明是个二级索引,但是它是唯一索引,体会下,首先对于 id = 10这个二级索引肯定要加锁,要不要锁 gap 呢,不用,因为是唯一索引,id = 10 只可能有这一条记录,然后呢,这样是不是就好了,还不行,因为啥,因为它是二级索引,对应的主键索引的记录才是真正的数据,万一被更新掉了咋办,所以在 id = 10 对应的主键索引上也需要加上锁(默认都是 record lock行锁),那主键索引上要不要加 gap 呢,也不用,也是精确定位到这一条记录
最后呢,当 id 不是主键,也不是唯一索引,只是个普通的索引,这里就需要大名鼎鼎的 gap 锁了,
是时候画个图了

其实核心的目的还是不让这个 id=10 的记录不会出现幻读,那么就需要在 id 这个索引上加上三个 gap 锁,主键索引上就不用了,在 id 索引上已经控制住了id = 10 不会出现幻读,主键索引上这两条对应的记录已经锁了,所以就这样 OK 了

上一篇聊了mysql 的 innodb 引擎基于 read view 实现的 mvcc 和事务隔离级别,可能有些细心的小伙伴会发现一些问题,第一个是在 RC 级别下的事务提交后的可见性,这里涉及到了三个参数,m_low_limit_id,m_up_limit_id,m_ids,之前看到知乎的一篇写的非常不错的文章,但是就在这一点上似乎有点疑惑,这里基于源码和注释来解释下这个问题

/**
+Nicksxs's Blog - What hurts more, the pain of hard work or the pain of regret?

Nicksxs's Blog

What hurts more, the pain of hard work or the pain of regret?

0%

看完前面两篇水文之后,感觉不得不来分析下 mysql 的锁了,其实前面说到幻读的时候是有个前提没提到的,比如一个select * from table1 where id = 1这种查询语句其实是不会加传说中的锁的,当然这里是指在 RR 或者 RC 隔离级别下,
看一段 mysql官方文档

SELECT ... FROM is a consistent read, reading a snapshot of the database and setting no locks unless the transaction isolation level is set to SERIALIZABLE. For SERIALIZABLE level, the search sets shared next-key locks on the index records it encounters. However, only an index record lock is required for statements that lock rows using a unique index to search for a unique row.

纯粹的这种一致性读,实际读取的是快照,也就是基于 read view 的读取方式,除非当前隔离级别是SERIALIZABLE
但是对于以下几类

  • select * from table where ? lock in share mode;
  • select * from table where ? for update;
  • insert into table values (...);
  • update table set ? where ?;
  • delete from table where ?;

除了第一条是 S 锁之外,其他都是 X 排他锁,这边在顺带下,S 锁表示共享锁, X 表示独占锁,同为 S 锁之间不冲突,S 与 X,X 与 S,X 与 X 之间都冲突,也就是加了前者,后者就加不上了
我们知道对于 RC 级别会出现幻读现象,对于 RR 级别不会出现,主要的区别是 RR 级别下对于以上的加锁读取都根据情况加上了 gap 锁,那么是不是 RR 级别下以上所有的都是要加 gap 锁呢,当然不是
举个例子,RR 事务隔离级别下,table1 有个主键id 字段
select * from table1 where id = 10 for update
这条语句要加 gap 锁吗?
答案是不需要,这里其实算是我看了这么久的一点自己的理解,啥时候要加 gap 锁,判断的条件是根据我查询的数据是否会因为不加 gap 锁而出现数量的不一致,我上面这条查询语句,在什么情况下会出现查询结果数量不一致呢,只要在这条记录被更新或者删除的时候,有没有可能我第一次查出来一条,第二次变成两条了呢,不可能,因为是主键索引。
再变更下这个题的条件,当 id 不是主键,但是是唯一索引,这样需要怎么加锁,注意问题是怎么加锁,不是需不需要加 gap 锁,这里呢就是稍微延伸一下,把聚簇索引(主键索引)和二级索引带一下,当 id 不是主键,说明是个二级索引,但是它是唯一索引,体会下,首先对于 id = 10这个二级索引肯定要加锁,要不要锁 gap 呢,不用,因为是唯一索引,id = 10 只可能有这一条记录,然后呢,这样是不是就好了,还不行,因为啥,因为它是二级索引,对应的主键索引的记录才是真正的数据,万一被更新掉了咋办,所以在 id = 10 对应的主键索引上也需要加上锁(默认都是 record lock行锁),那主键索引上要不要加 gap 呢,也不用,也是精确定位到这一条记录
最后呢,当 id 不是主键,也不是唯一索引,只是个普通的索引,这里就需要大名鼎鼎的 gap 锁了,
是时候画个图了

其实核心的目的还是不让这个 id=10 的记录不会出现幻读,那么就需要在 id 这个索引上加上三个 gap 锁,主键索引上就不用了,在 id 索引上已经控制住了id = 10 不会出现幻读,主键索引上这两条对应的记录已经锁了,所以就这样 OK 了

上一篇聊了mysql 的 innodb 引擎基于 read view 实现的 mvcc 和事务隔离级别,可能有些细心的小伙伴会发现一些问题,第一个是在 RC 级别下的事务提交后的可见性,这里涉及到了三个参数,m_low_limit_id,m_up_limit_id,m_ids,之前看到知乎的一篇写的非常不错的文章,但是就在这一点上似乎有点疑惑,这里基于源码和注释来解释下这个问题

/**
 Opens a read view where exactly the transactions serialized before this
 point in time are seen in the view.
 @param id		Creator transaction id */
@@ -8,7 +8,7 @@ void ReadView::prepare(trx_id_t id) {
 
   m_creator_trx_id = id;
 
-  m_low_limit_no = m_low_limit_id = m_up_limit_id = trx_sys->max_trx_id;

m_low_limit_id赋的值是trx_sys->max_trx_id,代表的是当前系统最小的未分配的事务 id,所以呢,举个例子,当前有三个活跃事务,事务 id 分别是 100,200,300,而 m_up_limit_id = 100, m_low_limit_id = 301,当事务 id 是 200 的提交之后,它的更新就是可以被 100 和 300 看到,而不是说 m_ids 里没了 200,并且 200 比 100 大就应该不可见了

幻读

还有一个问题是幻读的问题,这貌似也是个高频面试题,啥意思呢,或者说跟它最常拿来比较的脏读,脏读是指读到了别的事务未提交的数据,因为未提交,严格意义上来讲,不一定是会被最后落到库里,可能会回滚,也就是在 read uncommitted 级别下会出现的问题,但是幻读不太一样,幻读是指两次查询的结果数量不一样,比如我查了第一次是 select * from table1 where id < 10 for update,查出来了一条结果 id 是 5,然后再查一下发现出来了一条 id 是 5,一条 id 是 7,那是不是有点尴尬了,其实呢这个点我觉得脏读跟幻读也比较是从原理层面来命名,如果第一次接触的同学发觉有点不理解也比较正常,因为从逻辑上讲总之都是数据不符合预期,但是基于源码层面其实是不同的情况,幻读是在原先的 read view 无法完全解决的,怎么解决呢,简单的来说就是锁咯,我们知道innodb 是基于 record lock 行锁的,但是貌似没有办法解决这种问题,那么 innodb 就引入了 gap lock 间隙锁,比如上面说的情况下,id 小于 10 的情况下,是都应该锁住的,gap lock 其实是基于索引结构来锁的,因为索引树除了树形结构之外,还有一个next record 的指针,gap lock 也是基于这个来锁的
看一下 mysql 的文档

SELECT … FOR UPDATE sets an exclusive next-key lock on every record the search encounters. However, only an index record lock is required for statements that lock rows using a unique index to search for a unique row.

对于一个 for update 查询,在 RR 级别下,会设置一个 next-key lock在每一条被查询到的记录上,next-lock 又是啥呢,其实就是 gap 锁和 record 锁的结合体,比如我在数据库里有 id 是 1,3,5,7,10,对于上面那条查询,查出来的结果就是 1,3,5,7,那么按照文档里描述的,对于这几条记录都会加上next-key lock,也就是(-∞, 1], (1, 3], (3, 5], (5, 7], (7, 10) 这些区间和记录会被锁起来,不让插入,再唠叨一下呢,就是其实如果是只读的事务,光 read view 一致性读就够了,如果是有写操作的呢,就需要锁了。

很久以前,有位面试官问到,你知道 mysql 的事务隔离级别吗,“额 O__O …,不太清楚”,完了之后我就去网上找相关的文章,找到了这篇MySQL 四种事务隔离级的说明, 文章写得特别好,看了这个就懂了各个事务隔离级别都是啥,不过看了这个之后多思考一下的话还是会发现问题,这么神奇的事务隔离级别是怎么实现的呢

其中 innodb 的事务隔离用到了标题里说到的 mvcc,Multiversion concurrency control, 直译过来就是多版本并发控制,先不讲这个究竟是个啥,考虑下如果纯猜测,这个事务隔离级别应该会是怎么样实现呢,愚钝的我想了下,可以在事务开始的时候拷贝一个表,这个可以支持 RR 级别,RC 级别就不支持了,而且要是个非常大的表,想想就不可行

腆着脸说虽然这个不可行,但是思路是对的,具体实行起来需要做一系列(肥肠多)的改动,首先根据我的理解,其实这个拷贝一个表是变成拷贝一条记录,但是如果有多个事务,那就得拷贝多次,这个问题其实可以借助版本管理系统来解释,在用版本管理系统,git 之类的之前,很原始的可能是开发完一个功能后,就打个压缩包用时间等信息命名,然后如果后面要找回这个就直接用这个压缩包的就行了,后来有了 svn,git 中心式和分布式的版本管理系统,它的一个特点是粒度可以控制到文件和代码行级别,对应的我们的 mysql 事务是不是也可以从一开始预想的表级别细化到行的级别,可能之前很多人都了解过,数据库的一行记录除了我们用户自定义的字段,还有一些额外的字段,去源码data0type.h里捞一下

/* Precise data types for system columns and the length of those columns;
+  m_low_limit_no = m_low_limit_id = m_up_limit_id = trx_sys->max_trx_id;

m_low_limit_id赋的值是trx_sys->max_trx_id,代表的是当前系统最小的未分配的事务 id,所以呢,举个例子,当前有三个活跃事务,事务 id 分别是 100,200,300,而 m_up_limit_id = 100, m_low_limit_id = 301,当事务 id 是 200 的提交之后,它的更新就是可以被 100 和 300 看到,而不是说 m_ids 里没了 200,并且 200 比 100 大就应该不可见了

幻读

还有一个问题是幻读的问题,这貌似也是个高频面试题,啥意思呢,或者说跟它最常拿来比较的脏读,脏读是指读到了别的事务未提交的数据,因为未提交,严格意义上来讲,不一定是会被最后落到库里,可能会回滚,也就是在 read uncommitted 级别下会出现的问题,但是幻读不太一样,幻读是指两次查询的结果数量不一样,比如我查了第一次是 select * from table1 where id < 10 for update,查出来了一条结果 id 是 5,然后再查一下发现出来了一条 id 是 5,一条 id 是 7,那是不是有点尴尬了,其实呢这个点我觉得脏读跟幻读也比较是从原理层面来命名,如果第一次接触的同学发觉有点不理解也比较正常,因为从逻辑上讲总之都是数据不符合预期,但是基于源码层面其实是不同的情况,幻读是在原先的 read view 无法完全解决的,怎么解决呢,简单的来说就是锁咯,我们知道innodb 是基于 record lock 行锁的,但是貌似没有办法解决这种问题,那么 innodb 就引入了 gap lock 间隙锁,比如上面说的情况下,id 小于 10 的情况下,是都应该锁住的,gap lock 其实是基于索引结构来锁的,因为索引树除了树形结构之外,还有一个next record 的指针,gap lock 也是基于这个来锁的
看一下 mysql 的文档

SELECT … FOR UPDATE sets an exclusive next-key lock on every record the search encounters. However, only an index record lock is required for statements that lock rows using a unique index to search for a unique row.

对于一个 for update 查询,在 RR 级别下,会设置一个 next-key lock在每一条被查询到的记录上,next-lock 又是啥呢,其实就是 gap 锁和 record 锁的结合体,比如我在数据库里有 id 是 1,3,5,7,10,对于上面那条查询,查出来的结果就是 1,3,5,7,那么按照文档里描述的,对于这几条记录都会加上next-key lock,也就是(-∞, 1], (1, 3], (3, 5], (5, 7], (7, 10) 这些区间和记录会被锁起来,不让插入,再唠叨一下呢,就是其实如果是只读的事务,光 read view 一致性读就够了,如果是有写操作的呢,就需要锁了。

很久以前,有位面试官问到,你知道 mysql 的事务隔离级别吗,“额 O__O …,不太清楚”,完了之后我就去网上找相关的文章,找到了这篇MySQL 四种事务隔离级的说明, 文章写得特别好,看了这个就懂了各个事务隔离级别都是啥,不过看了这个之后多思考一下的话还是会发现问题,这么神奇的事务隔离级别是怎么实现的呢

其中 innodb 的事务隔离用到了标题里说到的 mvcc,Multiversion concurrency control, 直译过来就是多版本并发控制,先不讲这个究竟是个啥,考虑下如果纯猜测,这个事务隔离级别应该会是怎么样实现呢,愚钝的我想了下,可以在事务开始的时候拷贝一个表,这个可以支持 RR 级别,RC 级别就不支持了,而且要是个非常大的表,想想就不可行

腆着脸说虽然这个不可行,但是思路是对的,具体实行起来需要做一系列(肥肠多)的改动,首先根据我的理解,其实这个拷贝一个表是变成拷贝一条记录,但是如果有多个事务,那就得拷贝多次,这个问题其实可以借助版本管理系统来解释,在用版本管理系统,git 之类的之前,很原始的可能是开发完一个功能后,就打个压缩包用时间等信息命名,然后如果后面要找回这个就直接用这个压缩包的就行了,后来有了 svn,git 中心式和分布式的版本管理系统,它的一个特点是粒度可以控制到文件和代码行级别,对应的我们的 mysql 事务是不是也可以从一开始预想的表级别细化到行的级别,可能之前很多人都了解过,数据库的一行记录除了我们用户自定义的字段,还有一些额外的字段,去源码data0type.h里捞一下

/* Precise data types for system columns and the length of those columns;
 NOTE: the values must run from 0 up in the order given! All codes must
 be less than 256 */
 #define DATA_ROW_ID 0     /* row id: a 48-bit integer */
diff --git a/search.xml b/search.xml
index e3ba28fa10..a3bd2e7da6 100644
--- a/search.xml
+++ b/search.xml
@@ -117,164 +117,346 @@
       
   
   
-    34_Search_for_a_Range
-    /2016/08/14/34-Search-for-a-Range/
-    question

34. Search for a Range

Original Page

-

Given a sorted array of integers, find the starting and ending position of a given target value.

-

Your algorithm’s runtime complexity must be in the order of O(log n).

-

If the target is not found in the array, return [-1, -1].

-

For example,
Given [5, 7, 7, 8, 8, 10] and target value 8,
return [3, 4].

-

analysis

一开始就想到了二分查找,但是原来做二分查找的时候一般都是找到确定的那个数就完成了,
这里的情况比较特殊,需要找到整个区间,所以需要两遍查找,并且一个是找到小于target
的最大索引,一个是找到大于target的最大索引,代码参考leetcode discuss,这位仁
兄也做了详细的分析解释。

-

code

class Solution {
-public:
-    vector<int> searchRange(vector<int>& nums, int target) {
-        vector<int> ret(2, -1);
-        int i = 0, j = nums.size() - 1;
-        int mid;
-        while(i < j){
-            mid = (i + j) / 2;
-            if(nums[mid] < target) i = mid + 1;
-            else j = mid;
-        }
-        if(nums[i] != target) return ret;
-        else {
-            ret[0] = i;
-            if((i+1) < (nums.size() - 1) && nums[i+1] > target){
-                ret[1] = i;
-                return ret;
-            }
-        }   //一点小优化
-        j = nums.size() - 1;
-        while(i < j){
-            mid = (i + j) / 2 + 1;
-            if(nums[mid] > target) j = mid - 1;
-            else i = mid;
-        }
-        ret[1] = j;
-        return ret;
-    }
-};
]]>
- - leetcode - - - leetcode - c++ - -
- - 2021 年终总结 - /2022/01/22/2021-%E5%B9%B4%E7%BB%88%E6%80%BB%E7%BB%93/ - 又是一年年终总结,本着极度讨厌实时需求的理念,我还是 T+N 发布这个年终总结

-

工作篇

工作没什么大变化,有了些微的提升,可能因为是来了之后做了些项目对比公司与来还算是比较重要的,但是技术难度上没有特别突出的点,可能最开始用 openresty+lua 做了个 ab 测的工具,还是让我比较满意的,后面一般都是业务型的需求,今年可能在业务相关的技术逻辑上有了一些深度的了解,而原来一直想做的业务架构升级和通用型技术中间件这样的优化还是停留在想象中,前面说的 ab 测应该算是个半成品,还是没能多走出这一步,得需要多做一些实在的事情,比如轻量级的业务框架,能够对原先不熟悉的业务逻辑,代码逻辑有比较深入的理解,而不是一直都是让特定的同学负责特定的逻辑,很多时候还是在偷懒,习惯以一些简单安全的方案去做事情,在技术上还是要有所追求,还有就是能够在新语言,主要是 rust,swift 这类的能有些小玩具可以做,rust 的话是因为今年看了一本相关的书,后面三分之一其实消化得不好,这本书整体来说是很不错的,只是 rust 本身在所有权这块,还有引用包装等方面是设计得比较难懂,也可能是我基础差,所以还是想在复习下,可以做一个简单的命令行工具这种,然后 swift 是想说可以做点 mac 的小软件,原生的毕竟性能好点,又小。基于 web 做的客户端大部分都是又丑又大,极少数能好看点,但也是很重,起码 7~80M 的大小,原生的估计能除以 10。
整体的职业规划貌似陷入了比较大的困惑期,在目前公司发展前景不是很大,但是出去貌似也没有比较适合我的机会,总的来说还是杭州比较卷,个人觉得有自己的时间是非常重要的,而且这个不光是用来自我提升的,还是让自己有足够的时间做缓冲,有足够的时间锻炼减肥,时间少的情况下,不光会在仅有的时间里暴饮暴食,还没空锻炼,身体是革命的本钱,现在其实能特别明显地感觉到身体状态下滑,容易疲劳,焦虑。所以是否也许有可能以后要往外企这类的方向去发展。
工作上其实还是有个不大不小的缺点,就是容易激动,容易焦虑,前一点可能有稍稍地改观,因为工作中的很多现状其实是我个人难以改变的,即使觉得不合理,但是结构在那里,还不如自己放宽心,尽量做好事情就行。第二点的话还是做得比较差,一直以来抗压能力都比较差,跟成长环境,家庭环境都有比较大的关系,而且说实在的特别是父母,基本也没有在这方面给我正向的帮助,比较擅长给我施压,从小就是通过压力让我好好读书,当个乖学生,考个好学校,并没有能真正地理解我的压力,教我或者帮助我解压,只会在那说着不着边际的空话,甚至经常反过来对我施压。还是希望能慢慢解开,这点可能对我身体也有影响,也许需要看一些心理疏导相关的书籍。工作篇暂时到这,后续还有其他篇,未完待续哈哈😀

-]]>
- - 生活 - 年终总结 - - - 生活 - 年终总结 - 2021 - 拖更 - -
- - AQS篇二 之 Condition 浅析笔记 - /2021/02/21/AQS-%E4%B9%8B-Condition-%E6%B5%85%E6%9E%90%E7%AC%94%E8%AE%B0/ - Condition也是 AQS 中很重要的一块内容,可以先看段示例代码,这段代码应该来自于Doug Lea大大,可以在 javadoc 中的 condition 部分找到,其实大大原来写过基于 synchronized 实现的,后面我也贴下代码

-
import java.util.concurrent.locks.Condition;
-import java.util.concurrent.locks.Lock;
-import java.util.concurrent.locks.ReentrantLock;
-
-class BoundedBuffer {
-    final Lock lock = new ReentrantLock();
-    // condition 依赖于 lock 来产生
-    final Condition notFull = lock.newCondition();
-    final Condition notEmpty = lock.newCondition();
+    AQS篇一
+    /2021/02/14/AQS%E7%AF%87%E4%B8%80/
+    很多东西都是时看时新,而且时间长了也会忘,所以再来复习下,也会有一些新的角度看法这次来聊下AQS的内容,主要是这几个点,

+

第一个线程

第一个线程抢到锁了,此时state跟阻塞队列是怎么样的,其实这里是之前没理解对的地方

+
/**
+         * Fair version of tryAcquire.  Don't grant access unless
+         * recursive call or no waiters or is first.
+         */
+        protected final boolean tryAcquire(int acquires) {
+            final Thread current = Thread.currentThread();
+            int c = getState();
+            // 这里如果state还是0说明锁还空着
+            if (c == 0) {
+                // 因为是公平锁版本的,先去看下是否阻塞队列里有排着队的
+                if (!hasQueuedPredecessors() &&
+                    compareAndSetState(0, acquires)) {
+                    // 没有排队的,并且state使用cas设置成功的就标记当前占有锁的线程是我
+                    setExclusiveOwnerThread(current);
+                    // 然后其实就返回了,包括阻塞队列的head和tail节点和waitStatus都没有设置
+                    return true;
+                }
+            }
+            else if (current == getExclusiveOwnerThread()) {
+                int nextc = c + acquires;
+                if (nextc < 0)
+                    throw new Error("Maximum lock count exceeded");
+                setState(nextc);
+                return true;
+            }
+            // 这里就是第二个线程会返回false
+            return false;
+        }
+    }
- // 对象池子,put 跟 take 的就是这里的 - final Object[] items = new Object[100]; - int putptr, takeptr, count; +

第二个线程

当第二个线程进来的时候应该是怎么样,结合代码来看

+
/**
+     * Acquires in exclusive mode, ignoring interrupts.  Implemented
+     * by invoking at least once {@link #tryAcquire},
+     * returning on success.  Otherwise the thread is queued, possibly
+     * repeatedly blocking and unblocking, invoking {@link
+     * #tryAcquire} until success.  This method can be used
+     * to implement method {@link Lock#lock}.
+     *
+     * @param arg the acquire argument.  This value is conveyed to
+     *        {@link #tryAcquire} but is otherwise uninterpreted and
+     *        can represent anything you like.
+     */
+    public final void acquire(int arg) {
+        // 前面第一种情况是tryAcquire直接成功了,这个if判断第一个条件就是false,就不往下执行了
+        // 如果是第二个线程,第一个条件获取锁不成功,条件判断!tryAcquire(arg) == true,就会走
+        // acquireQueued(addWaiter(Node.EXCLUSIVE), arg)
+        if (!tryAcquire(arg) &&
+            acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
+            selfInterrupt();
+    }
- // 生产 - public void put(Object x) throws InterruptedException { - // 这里也说明了,需要先拥有锁 - lock.lock(); - try { - while (count == items.length) - notFull.await(); // 队列已满,等待,直到 not full 才能继续生产 - items[putptr] = x; - if (++putptr == items.length) putptr = 0; - ++count; - notEmpty.signal(); // 生产成功,队列已经 not empty 了,发个通知出去 - } finally { - lock.unlock(); +

然后来看下addWaiter的逻辑

+
/**
+     * Creates and enqueues node for current thread and given mode.
+     *
+     * @param mode Node.EXCLUSIVE for exclusive, Node.SHARED for shared
+     * @return the new node
+     */
+    private Node addWaiter(Node mode) {
+        // 这里是包装成一个node
+        Node node = new Node(Thread.currentThread(), mode);
+        // Try the fast path of enq; backup to full enq on failure
+        // 最快的方式就是把当前线程的节点放在阻塞队列的最后
+        Node pred = tail;
+        // 只有当tail,也就是pred不为空的时候可以直接接上
+        if (pred != null) {
+            node.prev = pred;
+            // 如果这里cas成功了,就直接接上返回了
+            if (compareAndSetTail(pred, node)) {
+                pred.next = node;
+                return node;
+            }
         }
-    }
+        // 不然就会继续走到这里
+        enq(node);
+        return node;
+    }
- // 消费 - public Object take() throws InterruptedException { - lock.lock(); - try { - while (count == 0) - notEmpty.await(); // 队列为空,等待,直到队列 not empty,才能继续消费 - Object x = items[takeptr]; - if (++takeptr == items.length) takeptr = 0; - --count; - notFull.signal(); // 被我消费掉一个,队列 not full 了,发个通知出去 - return x; - } finally { - lock.unlock(); +

然后就是enq的逻辑了

+
/**
+     * Inserts node into queue, initializing if necessary. See picture above.
+     * @param node the node to insert
+     * @return node's predecessor
+     */
+    private Node enq(final Node node) {
+        for (;;) {
+            // 如果状态没变化的话,tail这时还是null的
+            Node t = tail;
+            if (t == null) { // Must initialize
+                // 这里就会初始化头结点,就是个空节点
+                if (compareAndSetHead(new Node()))
+                    // tail也赋值成head
+                    tail = head;
+            } else {
+                // 这里就设置tail了
+                node.prev = t;
+                if (compareAndSetTail(t, node)) {
+                    t.next = node;
+                    return t;
+                }
+            }
         }
-    }
-}
+ }
-

介绍下 Condition 的结构

-
public class ConditionObject implements Condition, java.io.Serializable {
-        private static final long serialVersionUID = 1173984872572414699L;
-        /** First node of condition queue. */
-        private transient Node firstWaiter;
-        /** Last node of condition queue. */
-        private transient Node lastWaiter;
-

主要的就这么点,而且也复用了 AQS 阻塞队列或者大大叫 lock queue中同样的 Node 节点,只不过它没有使用其中的双向队列,也就是prev 和 next,而是在 Node 中的 nextWaiter,所以只是个单向的队列,没使用 next 其实还有个用处,后面会提到,看下结构的示意图

然后主要是看两个方法,awaitsignal,
先来看下 await

+

所以从这里可以看出来,其实head头结点不是个真实的带有线程的节点,并且不是在第一个线程进来的时候设置的

+

解锁

通过代码来看下

/**
- * Implements interruptible condition wait.
- * <ol>
- * <li> If current thread is interrupted, throw InterruptedException.
- * <li> Save lock state returned by {@link #getState}.
- * <li> Invoke {@link #release} with saved state as argument,
- *      throwing IllegalMonitorStateException if it fails.
- * <li> Block until signalled or interrupted.
- * <li> Reacquire by invoking specialized version of
- *      {@link #acquire} with saved state as argument.
- * <li> If interrupted while blocked in step 4, throw InterruptedException.
- * </ol>
- */
-public final void await() throws InterruptedException {
-    if (Thread.interrupted())
-        throw new InterruptedException();
-
-    // 将当前节点包装成一个  condition waiter node 节点
-    Node node = addConditionWaiter();
-
-    // 完全释放占有的锁,这里需要是占有锁的线程
-    int savedState = fullyRelease(node);
-    int interruptMode = 0;
-
-    // 判断下是否在阻塞队列中,因为有可能被其他节点从等待队列移动到阻塞队列
-    while (!isOnSyncQueue(node)) {
-        // park等待,等待被唤醒
-        LockSupport.park(this);
-        if ((interruptMode = checkInterruptWhileWaiting(node)) != 0)
-            break;
-    }
-
-    // 被唤醒后进入阻塞队列,等待获取锁,这里继续用了fullyRelease返回的 state
-    if (acquireQueued(node, savedState) && interruptMode != THROW_IE)
-        interruptMode = REINTERRUPT;
-    if (node.nextWaiter != null) // clean up if cancelled
-        unlinkCancelledWaiters();
+     * Attempts to release this lock.
+     *
+     * <p>If the current thread is the holder of this lock then the hold
+     * count is decremented.  If the hold count is now zero then the lock
+     * is released.  If the current thread is not the holder of this
+     * lock then {@link IllegalMonitorStateException} is thrown.
+     *
+     * @throws IllegalMonitorStateException if the current thread does not
+     *         hold this lock
+     */
+    public void unlock() {
+        // 释放锁
+        sync.release(1);
+    }
+/**
+     * Releases in exclusive mode.  Implemented by unblocking one or
+     * more threads if {@link #tryRelease} returns true.
+     * This method can be used to implement method {@link Lock#unlock}.
+     *
+     * @param arg the release argument.  This value is conveyed to
+     *        {@link #tryRelease} but is otherwise uninterpreted and
+     *        can represent anything you like.
+     * @return the value returned from {@link #tryRelease}
+     */
+    public final boolean release(int arg) {
+        // 尝试去释放
+        if (tryRelease(arg)) {
+            Node h = head;
+            if (h != null && h.waitStatus != 0)
+                unparkSuccessor(h);
+            return true;
+        }
+        return false;
+    }
+protected final boolean tryRelease(int releases) {
+            int c = getState() - releases;
+            if (Thread.currentThread() != getExclusiveOwnerThread())
+                throw new IllegalMonitorStateException();
+            boolean free = false;
+    		// 判断是否完全释放锁,因为可重入
+            if (c == 0) {
+                free = true;
+                setExclusiveOwnerThread(null);
+            }
+            setState(c);
+            return free;
+        }
+// 这段代码和上面的一致,只是为了顺序性,又拷下来看下
+
+public final boolean release(int arg) {
+        // 尝试去释放,如果是完全释放,返回的就是true,否则是false
+        if (tryRelease(arg)) {
+            Node h = head;
+            // 这里判断头结点是否为空以及waitStatus的状态,前面说了head节点其实是
+            // 在第二个线程进来的时候初始化的,如果是空的话说明没后续节点,并且waitStatus
+            // 也表示了后续的等待状态
+            if (h != null && h.waitStatus != 0)
+                unparkSuccessor(h);
+            return true;
+        }
+        return false;
+    }
+
+/**
+     * Wakes up node's successor, if one exists.
+     *
+     * @param node the node
+     */
+// 唤醒后继节点
+    private void unparkSuccessor(Node node) {
+        /*
+         * If status is negative (i.e., possibly needing signal) try
+         * to clear in anticipation of signalling.  It is OK if this
+         * fails or if status is changed by waiting thread.
+         */
+        int ws = node.waitStatus;
+        if (ws < 0)
+            compareAndSetWaitStatus(node, ws, 0);
+
+        /*
+         * Thread to unpark is held in successor, which is normally
+         * just the next node.  But if cancelled or apparently null,
+         * traverse backwards from tail to find the actual
+         * non-cancelled successor.
+         */
+        Node s = node.next;
+        // 如果后继节点是空或者当前节点取消等待了
+        if (s == null || s.waitStatus > 0) {
+            s = null;
+            // 从后往前找,找到非取消的节点,注意这里不是找到就退出,而是一直找到头
+            // 所以不必担心中间有取消的
+            for (Node t = tail; t != null && t != node; t = t.prev)
+                if (t.waitStatus <= 0)
+                    s = t;
+        }
+        if (s != null)
+            // 将其唤醒
+            LockSupport.unpark(s.thread);
+    }
+ + + + +]]>
+ + Java + 并发 + + + java + 并发 + j.u.c + aqs + +
+ + 2021 年终总结 + /2022/01/22/2021-%E5%B9%B4%E7%BB%88%E6%80%BB%E7%BB%93/ + 又是一年年终总结,本着极度讨厌实时需求的理念,我还是 T+N 发布这个年终总结

+

工作篇

工作没什么大变化,有了些微的提升,可能因为是来了之后做了些项目对比公司与来还算是比较重要的,但是技术难度上没有特别突出的点,可能最开始用 openresty+lua 做了个 ab 测的工具,还是让我比较满意的,后面一般都是业务型的需求,今年可能在业务相关的技术逻辑上有了一些深度的了解,而原来一直想做的业务架构升级和通用型技术中间件这样的优化还是停留在想象中,前面说的 ab 测应该算是个半成品,还是没能多走出这一步,得需要多做一些实在的事情,比如轻量级的业务框架,能够对原先不熟悉的业务逻辑,代码逻辑有比较深入的理解,而不是一直都是让特定的同学负责特定的逻辑,很多时候还是在偷懒,习惯以一些简单安全的方案去做事情,在技术上还是要有所追求,还有就是能够在新语言,主要是 rust,swift 这类的能有些小玩具可以做,rust 的话是因为今年看了一本相关的书,后面三分之一其实消化得不好,这本书整体来说是很不错的,只是 rust 本身在所有权这块,还有引用包装等方面是设计得比较难懂,也可能是我基础差,所以还是想在复习下,可以做一个简单的命令行工具这种,然后 swift 是想说可以做点 mac 的小软件,原生的毕竟性能好点,又小。基于 web 做的客户端大部分都是又丑又大,极少数能好看点,但也是很重,起码 7~80M 的大小,原生的估计能除以 10。
整体的职业规划貌似陷入了比较大的困惑期,在目前公司发展前景不是很大,但是出去貌似也没有比较适合我的机会,总的来说还是杭州比较卷,个人觉得有自己的时间是非常重要的,而且这个不光是用来自我提升的,还是让自己有足够的时间做缓冲,有足够的时间锻炼减肥,时间少的情况下,不光会在仅有的时间里暴饮暴食,还没空锻炼,身体是革命的本钱,现在其实能特别明显地感觉到身体状态下滑,容易疲劳,焦虑。所以是否也许有可能以后要往外企这类的方向去发展。
工作上其实还是有个不大不小的缺点,就是容易激动,容易焦虑,前一点可能有稍稍地改观,因为工作中的很多现状其实是我个人难以改变的,即使觉得不合理,但是结构在那里,还不如自己放宽心,尽量做好事情就行。第二点的话还是做得比较差,一直以来抗压能力都比较差,跟成长环境,家庭环境都有比较大的关系,而且说实在的特别是父母,基本也没有在这方面给我正向的帮助,比较擅长给我施压,从小就是通过压力让我好好读书,当个乖学生,考个好学校,并没有能真正地理解我的压力,教我或者帮助我解压,只会在那说着不着边际的空话,甚至经常反过来对我施压。还是希望能慢慢解开,这点可能对我身体也有影响,也许需要看一些心理疏导相关的书籍。工作篇暂时到这,后续还有其他篇,未完待续哈哈😀

+]]>
+ + 生活 + 年终总结 + + + 生活 + 年终总结 + 2021 + 拖更 + +
+ + AQS篇二 之 Condition 浅析笔记 + /2021/02/21/AQS-%E4%B9%8B-Condition-%E6%B5%85%E6%9E%90%E7%AC%94%E8%AE%B0/ + Condition也是 AQS 中很重要的一块内容,可以先看段示例代码,这段代码应该来自于Doug Lea大大,可以在 javadoc 中的 condition 部分找到,其实大大原来写过基于 synchronized 实现的,后面我也贴下代码

+
import java.util.concurrent.locks.Condition;
+import java.util.concurrent.locks.Lock;
+import java.util.concurrent.locks.ReentrantLock;
+
+class BoundedBuffer {
+    final Lock lock = new ReentrantLock();
+    // condition 依赖于 lock 来产生
+    final Condition notFull = lock.newCondition();
+    final Condition notEmpty = lock.newCondition();
+
+    // 对象池子,put 跟 take 的就是这里的
+    final Object[] items = new Object[100];
+    int putptr, takeptr, count;
+
+    // 生产
+    public void put(Object x) throws InterruptedException {
+        // 这里也说明了,需要先拥有锁
+        lock.lock();
+        try {
+            while (count == items.length)
+                notFull.await();  // 队列已满,等待,直到 not full 才能继续生产
+            items[putptr] = x;
+            if (++putptr == items.length) putptr = 0;
+            ++count;
+            notEmpty.signal(); // 生产成功,队列已经 not empty 了,发个通知出去
+        } finally {
+            lock.unlock();
+        }
+    }
+
+    // 消费
+    public Object take() throws InterruptedException {
+        lock.lock();
+        try {
+            while (count == 0)
+                notEmpty.await(); // 队列为空,等待,直到队列 not empty,才能继续消费
+            Object x = items[takeptr];
+            if (++takeptr == items.length) takeptr = 0;
+            --count;
+            notFull.signal(); // 被我消费掉一个,队列 not full 了,发个通知出去
+            return x;
+        } finally {
+            lock.unlock();
+        }
+    }
+}
+ +

介绍下 Condition 的结构

+
public class ConditionObject implements Condition, java.io.Serializable {
+        private static final long serialVersionUID = 1173984872572414699L;
+        /** First node of condition queue. */
+        private transient Node firstWaiter;
+        /** Last node of condition queue. */
+        private transient Node lastWaiter;
+

主要的就这么点,而且也复用了 AQS 阻塞队列或者大大叫 lock queue中同样的 Node 节点,只不过它没有使用其中的双向队列,也就是prev 和 next,而是在 Node 中的 nextWaiter,所以只是个单向的队列,没使用 next 其实还有个用处,后面会提到,看下结构的示意图

然后主要是看两个方法,awaitsignal,
先来看下 await

+
/**
+ * Implements interruptible condition wait.
+ * <ol>
+ * <li> If current thread is interrupted, throw InterruptedException.
+ * <li> Save lock state returned by {@link #getState}.
+ * <li> Invoke {@link #release} with saved state as argument,
+ *      throwing IllegalMonitorStateException if it fails.
+ * <li> Block until signalled or interrupted.
+ * <li> Reacquire by invoking specialized version of
+ *      {@link #acquire} with saved state as argument.
+ * <li> If interrupted while blocked in step 4, throw InterruptedException.
+ * </ol>
+ */
+public final void await() throws InterruptedException {
+    if (Thread.interrupted())
+        throw new InterruptedException();
+
+    // 将当前节点包装成一个  condition waiter node 节点
+    Node node = addConditionWaiter();
+
+    // 完全释放占有的锁,这里需要是占有锁的线程
+    int savedState = fullyRelease(node);
+    int interruptMode = 0;
+
+    // 判断下是否在阻塞队列中,因为有可能被其他节点从等待队列移动到阻塞队列
+    while (!isOnSyncQueue(node)) {
+        // park等待,等待被唤醒
+        LockSupport.park(this);
+        if ((interruptMode = checkInterruptWhileWaiting(node)) != 0)
+            break;
+    }
+
+    // 被唤醒后进入阻塞队列,等待获取锁,这里继续用了fullyRelease返回的 state
+    if (acquireQueued(node, savedState) && interruptMode != THROW_IE)
+        interruptMode = REINTERRUPT;
+    if (node.nextWaiter != null) // clean up if cancelled
+        unlinkCancelledWaiters();
     if (interruptMode != 0)
         reportInterruptAfterWait(interruptMode);
 }
@@ -677,26 +859,138 @@ public:
- add-two-number - /2015/04/14/Add-Two-Number/ - problem

You are given two linked lists representing two non-negative numbers. The digits are stored in reverse order and each of their nodes contain a single digit. Add the two numbers and return it as a linked list.

-

Input:(2 -> 4 -> 3) + (5 -> 6 -> 4)
Output: 7 -> 0 -> 8

-

分析(不用英文装逼了)
这个代码是抄来的,链接原作是这位大大。

- -

一开始没看懂题,后来发现是要进位的,自己写的时候想把长短不同时长的串接到结果
串的后面,试了下因为进位会有些问题比较难搞定,这样的话就是在其中一个为空的
时候还是会循环操作,在链表太大的时候可能会有问题,就这样(逃
原来是有个小错误没发现,改进后的代码也AC了,棒棒哒!

-

正确代码

/**
- * Definition for singly-linked list.
- * struct ListNode {
- *     int val;
- *     ListNode *next;
- *     ListNode(int x) : val(x), next(NULL) {}
- * };
- */
-class Solution {
+    34_Search_for_a_Range
+    /2016/08/14/34-Search-for-a-Range/
+    question

34. Search for a Range

Original Page

+

Given a sorted array of integers, find the starting and ending position of a given target value.

+

Your algorithm’s runtime complexity must be in the order of O(log n).

+

If the target is not found in the array, return [-1, -1].

+

For example,
Given [5, 7, 7, 8, 8, 10] and target value 8,
return [3, 4].

+

analysis

一开始就想到了二分查找,但是原来做二分查找的时候一般都是找到确定的那个数就完成了,
这里的情况比较特殊,需要找到整个区间,所以需要两遍查找,并且一个是找到小于target
的最大索引,一个是找到大于target的最大索引,代码参考leetcode discuss,这位仁
兄也做了详细的分析解释。

+

code

class Solution {
 public:
-    ListNode *addTwoNumbers(ListNode *l1, ListNode *l2) {
-        ListNode dummy(0);
-        ListNode* p = &dummy;
+    vector<int> searchRange(vector<int>& nums, int target) {
+        vector<int> ret(2, -1);
+        int i = 0, j = nums.size() - 1;
+        int mid;
+        while(i < j){
+            mid = (i + j) / 2;
+            if(nums[mid] < target) i = mid + 1;
+            else j = mid;
+        }
+        if(nums[i] != target) return ret;
+        else {
+            ret[0] = i;
+            if((i+1) < (nums.size() - 1) && nums[i+1] > target){
+                ret[1] = i;
+                return ret;
+            }
+        }   //一点小优化
+        j = nums.size() - 1;
+        while(i < j){
+            mid = (i + j) / 2 + 1;
+            if(nums[mid] > target) j = mid - 1;
+            else i = mid;
+        }
+        ret[1] = j;
+        return ret;
+    }
+};
]]>
+ + leetcode + + + leetcode + c++ + + + + AbstractQueuedSynchronizer + /2019/09/23/AbstractQueuedSynchronizer/ + 最近看了大神的 AQS 的文章,之前总是断断续续地看一点,每次都知难而退,下次看又从头开始,昨天总算硬着头皮看完了第一部分
首先 AQS 只要有这些属性

+
// 头结点,你直接把它当做 当前持有锁的线程 可能是最好理解的
+private transient volatile Node head;
+
+// 阻塞的尾节点,每个新的节点进来,都插入到最后,也就形成了一个链表
+private transient volatile Node tail;
+
+// 这个是最重要的,代表当前锁的状态,0代表没有被占用,大于 0 代表有线程持有当前锁
+// 这个值可以大于 1,是因为锁可以重入,每次重入都加上 1
+private volatile int state;
+
+// 代表当前持有独占锁的线程,举个最重要的使用例子,因为锁可以重入
+// reentrantLock.lock()可以嵌套调用多次,所以每次用这个来判断当前线程是否已经拥有了锁
+// if (currentThread == getExclusiveOwnerThread()) {state++}
+private transient Thread exclusiveOwnerThread; //继承自AbstractOwnableSynchronizer
+

大概了解了 aqs 底层的双向等待队列,
结构是这样的

每个 node 里面主要是的代码结构也比较简单

+
static final class Node {
+    // 标识节点当前在共享模式下
+    static final Node SHARED = new Node();
+    // 标识节点当前在独占模式下
+    static final Node EXCLUSIVE = null;
+
+    // ======== 下面的几个int常量是给waitStatus用的 ===========
+    /** waitStatus value to indicate thread has cancelled */
+    // 代码此线程取消了争抢这个锁
+    static final int CANCELLED =  1;
+    /** waitStatus value to indicate successor's thread needs unparking */
+    // 官方的描述是,其表示当前node的后继节点对应的线程需要被唤醒
+    static final int SIGNAL    = -1;
+    /** waitStatus value to indicate thread is waiting on condition */
+    // 本文不分析condition,所以略过吧,下一篇文章会介绍这个
+    static final int CONDITION = -2;
+    /**
+     * waitStatus value to indicate the next acquireShared should
+     * unconditionally propagate
+     */
+    // 同样的不分析,略过吧
+    static final int PROPAGATE = -3;
+    // =====================================================
+
+
+    // 取值为上面的1、-1、-2、-3,或者0(以后会讲到)
+    // 这么理解,暂时只需要知道如果这个值 大于0 代表此线程取消了等待,
+    //    ps: 半天抢不到锁,不抢了,ReentrantLock是可以指定timeouot的。。。
+    volatile int waitStatus;
+    // 前驱节点的引用
+    volatile Node prev;
+    // 后继节点的引用
+    volatile Node next;
+    // 这个就是线程本尊
+    volatile Thread thread;
+
+}
+

其实可以主要关注这个 waitStatus 因为这个是后面的节点给前面的节点设置的,等于-1 的时候代表后面有节点等待,需要去唤醒,
这里使用了一个变种的 CLH 队列实现,CLH 队列相关内容可以查看这篇 自旋锁、排队自旋锁、MCS锁、CLH锁

+]]>
+ + java + + + java + aqs + +
+ + add-two-number + /2015/04/14/Add-Two-Number/ + problem

You are given two linked lists representing two non-negative numbers. The digits are stored in reverse order and each of their nodes contain a single digit. Add the two numbers and return it as a linked list.

+

Input:(2 -> 4 -> 3) + (5 -> 6 -> 4)
Output: 7 -> 0 -> 8

+

分析(不用英文装逼了)
这个代码是抄来的,链接原作是这位大大。

+ +

一开始没看懂题,后来发现是要进位的,自己写的时候想把长短不同时长的串接到结果
串的后面,试了下因为进位会有些问题比较难搞定,这样的话就是在其中一个为空的
时候还是会循环操作,在链表太大的时候可能会有问题,就这样(逃
原来是有个小错误没发现,改进后的代码也AC了,棒棒哒!

+

正确代码

/**
+ * Definition for singly-linked list.
+ * struct ListNode {
+ *     int val;
+ *     ListNode *next;
+ *     ListNode(int x) : val(x), next(NULL) {}
+ * };
+ */
+class Solution {
+public:
+    ListNode *addTwoNumbers(ListNode *l1, ListNode *l2) {
+        ListNode dummy(0);
+        ListNode* p = &dummy;
 
         int cn = 0;
         while(l1 || l2){
@@ -867,297 +1161,104 @@ public:
       
   
   
-    AbstractQueuedSynchronizer
-    /2019/09/23/AbstractQueuedSynchronizer/
-    最近看了大神的 AQS 的文章,之前总是断断续续地看一点,每次都知难而退,下次看又从头开始,昨天总算硬着头皮看完了第一部分
首先 AQS 只要有这些属性

-
// 头结点,你直接把它当做 当前持有锁的线程 可能是最好理解的
-private transient volatile Node head;
+    Comparator使用小记
+    /2020/04/05/Comparator%E4%BD%BF%E7%94%A8%E5%B0%8F%E8%AE%B0/
+    在Java8的stream之前,将对象进行排序的时候,可能需要对象实现Comparable接口,或者自己实现一个Comparator,

+

比如这样子

+

我的对象是Entity

+
public class Entity {
 
-// 阻塞的尾节点,每个新的节点进来,都插入到最后,也就形成了一个链表
-private transient volatile Node tail;
+    private Long id;
 
-// 这个是最重要的,代表当前锁的状态,0代表没有被占用,大于 0 代表有线程持有当前锁
-// 这个值可以大于 1,是因为锁可以重入,每次重入都加上 1
-private volatile int state;
+    private Long sortValue;
 
-// 代表当前持有独占锁的线程,举个最重要的使用例子,因为锁可以重入
-// reentrantLock.lock()可以嵌套调用多次,所以每次用这个来判断当前线程是否已经拥有了锁
-// if (currentThread == getExclusiveOwnerThread()) {state++}
-private transient Thread exclusiveOwnerThread; //继承自AbstractOwnableSynchronizer
-

大概了解了 aqs 底层的双向等待队列,
结构是这样的

每个 node 里面主要是的代码结构也比较简单

-
static final class Node {
-    // 标识节点当前在共享模式下
-    static final Node SHARED = new Node();
-    // 标识节点当前在独占模式下
-    static final Node EXCLUSIVE = null;
+    public Long getId() {
+        return id;
+    }
 
-    // ======== 下面的几个int常量是给waitStatus用的 ===========
-    /** waitStatus value to indicate thread has cancelled */
-    // 代码此线程取消了争抢这个锁
-    static final int CANCELLED =  1;
-    /** waitStatus value to indicate successor's thread needs unparking */
-    // 官方的描述是,其表示当前node的后继节点对应的线程需要被唤醒
-    static final int SIGNAL    = -1;
-    /** waitStatus value to indicate thread is waiting on condition */
-    // 本文不分析condition,所以略过吧,下一篇文章会介绍这个
-    static final int CONDITION = -2;
-    /**
-     * waitStatus value to indicate the next acquireShared should
-     * unconditionally propagate
-     */
-    // 同样的不分析,略过吧
-    static final int PROPAGATE = -3;
-    // =====================================================
+    public void setId(Long id) {
+        this.id = id;
+    }
 
+    public Long getSortValue() {
+        return sortValue;
+    }
 
-    // 取值为上面的1、-1、-2、-3,或者0(以后会讲到)
-    // 这么理解,暂时只需要知道如果这个值 大于0 代表此线程取消了等待,
-    //    ps: 半天抢不到锁,不抢了,ReentrantLock是可以指定timeouot的。。。
-    volatile int waitStatus;
-    // 前驱节点的引用
-    volatile Node prev;
-    // 后继节点的引用
-    volatile Node next;
-    // 这个就是线程本尊
-    volatile Thread thread;
+    public void setSortValue(Long sortValue) {
+        this.sortValue = sortValue;
+    }
+}
-}
-

其实可以主要关注这个 waitStatus 因为这个是后面的节点给前面的节点设置的,等于-1 的时候代表后面有节点等待,需要去唤醒,
这里使用了一个变种的 CLH 队列实现,CLH 队列相关内容可以查看这篇 自旋锁、排队自旋锁、MCS锁、CLH锁

+

Comparator

+
public class MyComparator implements Comparator {
+    @Override
+    public int compare(Object o1, Object o2) {
+        Entity e1 = (Entity) o1;
+        Entity e2 = (Entity) o2;
+        if (e1.getSortValue() < e2.getSortValue()) {
+            return -1;
+        } else if (e1.getSortValue().equals(e2.getSortValue())) {
+            return 0;
+        } else {
+            return 1;
+        }
+    }
+}
+ +

比较代码

+
private static MyComparator myComparator = new MyComparator();
+
+    public static void main(String[] args) {
+        List<Entity> list = new ArrayList<Entity>();
+        Entity e1 = new Entity();
+        e1.setId(1L);
+        e1.setSortValue(1L);
+        list.add(e1);
+        Entity e2 = new Entity();
+        e2.setId(2L);
+        e2.setSortValue(null);
+        list.add(e2);
+        Collections.sort(list, myComparator);
+ +

看到这里的e2的排序值是null,在Comparator中如果要正常运行的话,就得判空之类的,这里有两点需要,一个是不想写这个MyComparator,然后也没那么好排除掉list里排序值,那么有什么办法能解决这种问题呢,应该说java的这方面真的是很强大

+

+

看一下nullsFirst的实现

+
final static class NullComparator<T> implements Comparator<T>, Serializable {
+        private static final long serialVersionUID = -7569533591570686392L;
+        private final boolean nullFirst;
+        // if null, non-null Ts are considered equal
+        private final Comparator<T> real;
+
+        @SuppressWarnings("unchecked")
+        NullComparator(boolean nullFirst, Comparator<? super T> real) {
+            this.nullFirst = nullFirst;
+            this.real = (Comparator<T>) real;
+        }
+
+        @Override
+        public int compare(T a, T b) {
+            if (a == null) {
+                return (b == null) ? 0 : (nullFirst ? -1 : 1);
+            } else if (b == null) {
+                return nullFirst ? 1: -1;
+            } else {
+                return (real == null) ? 0 : real.compare(a, b);
+            }
+        }
+ +

核心代码就是下面这段,其实就是帮我们把前面要做的事情做掉了,是不是挺方便的,小记一下哈

]]>
- java + Java + 集合 - java - aqs - -
- - AQS篇一 - /2021/02/14/AQS%E7%AF%87%E4%B8%80/ - 很多东西都是时看时新,而且时间长了也会忘,所以再来复习下,也会有一些新的角度看法这次来聊下AQS的内容,主要是这几个点,

-

第一个线程

第一个线程抢到锁了,此时state跟阻塞队列是怎么样的,其实这里是之前没理解对的地方

-
/**
-         * Fair version of tryAcquire.  Don't grant access unless
-         * recursive call or no waiters or is first.
-         */
-        protected final boolean tryAcquire(int acquires) {
-            final Thread current = Thread.currentThread();
-            int c = getState();
-            // 这里如果state还是0说明锁还空着
-            if (c == 0) {
-                // 因为是公平锁版本的,先去看下是否阻塞队列里有排着队的
-                if (!hasQueuedPredecessors() &&
-                    compareAndSetState(0, acquires)) {
-                    // 没有排队的,并且state使用cas设置成功的就标记当前占有锁的线程是我
-                    setExclusiveOwnerThread(current);
-                    // 然后其实就返回了,包括阻塞队列的head和tail节点和waitStatus都没有设置
-                    return true;
-                }
-            }
-            else if (current == getExclusiveOwnerThread()) {
-                int nextc = c + acquires;
-                if (nextc < 0)
-                    throw new Error("Maximum lock count exceeded");
-                setState(nextc);
-                return true;
-            }
-            // 这里就是第二个线程会返回false
-            return false;
-        }
-    }
- -

第二个线程

当第二个线程进来的时候应该是怎么样,结合代码来看

-
/**
-     * Acquires in exclusive mode, ignoring interrupts.  Implemented
-     * by invoking at least once {@link #tryAcquire},
-     * returning on success.  Otherwise the thread is queued, possibly
-     * repeatedly blocking and unblocking, invoking {@link
-     * #tryAcquire} until success.  This method can be used
-     * to implement method {@link Lock#lock}.
-     *
-     * @param arg the acquire argument.  This value is conveyed to
-     *        {@link #tryAcquire} but is otherwise uninterpreted and
-     *        can represent anything you like.
-     */
-    public final void acquire(int arg) {
-        // 前面第一种情况是tryAcquire直接成功了,这个if判断第一个条件就是false,就不往下执行了
-        // 如果是第二个线程,第一个条件获取锁不成功,条件判断!tryAcquire(arg) == true,就会走
-        // acquireQueued(addWaiter(Node.EXCLUSIVE), arg)
-        if (!tryAcquire(arg) &&
-            acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
-            selfInterrupt();
-    }
- -

然后来看下addWaiter的逻辑

-
/**
-     * Creates and enqueues node for current thread and given mode.
-     *
-     * @param mode Node.EXCLUSIVE for exclusive, Node.SHARED for shared
-     * @return the new node
-     */
-    private Node addWaiter(Node mode) {
-        // 这里是包装成一个node
-        Node node = new Node(Thread.currentThread(), mode);
-        // Try the fast path of enq; backup to full enq on failure
-        // 最快的方式就是把当前线程的节点放在阻塞队列的最后
-        Node pred = tail;
-        // 只有当tail,也就是pred不为空的时候可以直接接上
-        if (pred != null) {
-            node.prev = pred;
-            // 如果这里cas成功了,就直接接上返回了
-            if (compareAndSetTail(pred, node)) {
-                pred.next = node;
-                return node;
-            }
-        }
-        // 不然就会继续走到这里
-        enq(node);
-        return node;
-    }
- -

然后就是enq的逻辑了

-
/**
-     * Inserts node into queue, initializing if necessary. See picture above.
-     * @param node the node to insert
-     * @return node's predecessor
-     */
-    private Node enq(final Node node) {
-        for (;;) {
-            // 如果状态没变化的话,tail这时还是null的
-            Node t = tail;
-            if (t == null) { // Must initialize
-                // 这里就会初始化头结点,就是个空节点
-                if (compareAndSetHead(new Node()))
-                    // tail也赋值成head
-                    tail = head;
-            } else {
-                // 这里就设置tail了
-                node.prev = t;
-                if (compareAndSetTail(t, node)) {
-                    t.next = node;
-                    return t;
-                }
-            }
-        }
-    }
- -

所以从这里可以看出来,其实head头结点不是个真实的带有线程的节点,并且不是在第一个线程进来的时候设置的

-

解锁

通过代码来看下

-
/**
-     * Attempts to release this lock.
-     *
-     * <p>If the current thread is the holder of this lock then the hold
-     * count is decremented.  If the hold count is now zero then the lock
-     * is released.  If the current thread is not the holder of this
-     * lock then {@link IllegalMonitorStateException} is thrown.
-     *
-     * @throws IllegalMonitorStateException if the current thread does not
-     *         hold this lock
-     */
-    public void unlock() {
-        // 释放锁
-        sync.release(1);
-    }
-/**
-     * Releases in exclusive mode.  Implemented by unblocking one or
-     * more threads if {@link #tryRelease} returns true.
-     * This method can be used to implement method {@link Lock#unlock}.
-     *
-     * @param arg the release argument.  This value is conveyed to
-     *        {@link #tryRelease} but is otherwise uninterpreted and
-     *        can represent anything you like.
-     * @return the value returned from {@link #tryRelease}
-     */
-    public final boolean release(int arg) {
-        // 尝试去释放
-        if (tryRelease(arg)) {
-            Node h = head;
-            if (h != null && h.waitStatus != 0)
-                unparkSuccessor(h);
-            return true;
-        }
-        return false;
-    }
-protected final boolean tryRelease(int releases) {
-            int c = getState() - releases;
-            if (Thread.currentThread() != getExclusiveOwnerThread())
-                throw new IllegalMonitorStateException();
-            boolean free = false;
-    		// 判断是否完全释放锁,因为可重入
-            if (c == 0) {
-                free = true;
-                setExclusiveOwnerThread(null);
-            }
-            setState(c);
-            return free;
-        }
-// 这段代码和上面的一致,只是为了顺序性,又拷下来看下
-
-public final boolean release(int arg) {
-        // 尝试去释放,如果是完全释放,返回的就是true,否则是false
-        if (tryRelease(arg)) {
-            Node h = head;
-            // 这里判断头结点是否为空以及waitStatus的状态,前面说了head节点其实是
-            // 在第二个线程进来的时候初始化的,如果是空的话说明没后续节点,并且waitStatus
-            // 也表示了后续的等待状态
-            if (h != null && h.waitStatus != 0)
-                unparkSuccessor(h);
-            return true;
-        }
-        return false;
-    }
-
-/**
-     * Wakes up node's successor, if one exists.
-     *
-     * @param node the node
-     */
-// 唤醒后继节点
-    private void unparkSuccessor(Node node) {
-        /*
-         * If status is negative (i.e., possibly needing signal) try
-         * to clear in anticipation of signalling.  It is OK if this
-         * fails or if status is changed by waiting thread.
-         */
-        int ws = node.waitStatus;
-        if (ws < 0)
-            compareAndSetWaitStatus(node, ws, 0);
-
-        /*
-         * Thread to unpark is held in successor, which is normally
-         * just the next node.  But if cancelled or apparently null,
-         * traverse backwards from tail to find the actual
-         * non-cancelled successor.
-         */
-        Node s = node.next;
-        // 如果后继节点是空或者当前节点取消等待了
-        if (s == null || s.waitStatus > 0) {
-            s = null;
-            // 从后往前找,找到非取消的节点,注意这里不是找到就退出,而是一直找到头
-            // 所以不必担心中间有取消的
-            for (Node t = tail; t != null && t != node; t = t.prev)
-                if (t.waitStatus <= 0)
-                    s = t;
-        }
-        if (s != null)
-            // 将其唤醒
-            LockSupport.unpark(s.thread);
-    }
- - - - -]]>
- - Java - 并发 - - - java - 并发 - j.u.c - aqs + Java + Stream + Comparator + 排序 + sort + nullsfirst
@@ -1214,119 +1315,18 @@ Node *clone(Node *graph) { - Comparator使用小记 - /2020/04/05/Comparator%E4%BD%BF%E7%94%A8%E5%B0%8F%E8%AE%B0/ - 在Java8的stream之前,将对象进行排序的时候,可能需要对象实现Comparable接口,或者自己实现一个Comparator,

-

比如这样子

-

我的对象是Entity

-
public class Entity {
-
-    private Long id;
-
-    private Long sortValue;
+    Disruptor 系列一
+    /2022/02/13/Disruptor-%E7%B3%BB%E5%88%97%E4%B8%80/
+    很久之前就听说过这个框架,不过之前有点跟消息队列混起来,这个也是种队列,但不是跟 rocketmq,nsq 那种一样的,而是在进程内部提供队列服务的,偏向于取代ArrayBlockingQueue,因为这个阻塞队列是使用了锁来控制阻塞,关于并发其实有一些通用的最佳实践,就是用锁,即使是 JDK 提供的锁,也是比较耗资源的,当然这是跟不加锁的对比,同样是锁,JDK 的实现还是性能比较优秀的。常见的阻塞队列中例如 ArrayBlockingQueueLinkedBlockingQueue 都有锁的身影的存在,区别在于 ArrayBlockingQueue 是一把锁,后者是两把锁,不过重点不在几把锁,这里其实是两个问题,一个是所谓的 lock free, 对于一个单生产者的 disruptor 来说,因为写入是只有一个线程的,是可以不用加锁,多生产者的时候使用的是 cas 来获取对应的写入坑位,另一个是解决“伪共享”问题,后面可以详细点分析,先介绍下使用
首先是数据源

+
public class LongEvent {
+    private long value;
 
-    public Long getId() {
-        return id;
+    public void set(long value) {
+        this.value = value;
     }
 
-    public void setId(Long id) {
-        this.id = id;
-    }
-
-    public Long getSortValue() {
-        return sortValue;
-    }
-
-    public void setSortValue(Long sortValue) {
-        this.sortValue = sortValue;
-    }
-}
- -

Comparator

-
public class MyComparator implements Comparator {
-    @Override
-    public int compare(Object o1, Object o2) {
-        Entity e1 = (Entity) o1;
-        Entity e2 = (Entity) o2;
-        if (e1.getSortValue() < e2.getSortValue()) {
-            return -1;
-        } else if (e1.getSortValue().equals(e2.getSortValue())) {
-            return 0;
-        } else {
-            return 1;
-        }
-    }
-}
- -

比较代码

-
private static MyComparator myComparator = new MyComparator();
-
-    public static void main(String[] args) {
-        List<Entity> list = new ArrayList<Entity>();
-        Entity e1 = new Entity();
-        e1.setId(1L);
-        e1.setSortValue(1L);
-        list.add(e1);
-        Entity e2 = new Entity();
-        e2.setId(2L);
-        e2.setSortValue(null);
-        list.add(e2);
-        Collections.sort(list, myComparator);
- -

看到这里的e2的排序值是null,在Comparator中如果要正常运行的话,就得判空之类的,这里有两点需要,一个是不想写这个MyComparator,然后也没那么好排除掉list里排序值,那么有什么办法能解决这种问题呢,应该说java的这方面真的是很强大

-

-

看一下nullsFirst的实现

-
final static class NullComparator<T> implements Comparator<T>, Serializable {
-        private static final long serialVersionUID = -7569533591570686392L;
-        private final boolean nullFirst;
-        // if null, non-null Ts are considered equal
-        private final Comparator<T> real;
-
-        @SuppressWarnings("unchecked")
-        NullComparator(boolean nullFirst, Comparator<? super T> real) {
-            this.nullFirst = nullFirst;
-            this.real = (Comparator<T>) real;
-        }
-
-        @Override
-        public int compare(T a, T b) {
-            if (a == null) {
-                return (b == null) ? 0 : (nullFirst ? -1 : 1);
-            } else if (b == null) {
-                return nullFirst ? 1: -1;
-            } else {
-                return (real == null) ? 0 : real.compare(a, b);
-            }
-        }
- -

核心代码就是下面这段,其实就是帮我们把前面要做的事情做掉了,是不是挺方便的,小记一下哈

-]]>
- - Java - 集合 - - - Java - Stream - Comparator - 排序 - sort - nullsfirst - - - - Disruptor 系列一 - /2022/02/13/Disruptor-%E7%B3%BB%E5%88%97%E4%B8%80/ - 很久之前就听说过这个框架,不过之前有点跟消息队列混起来,这个也是种队列,但不是跟 rocketmq,nsq 那种一样的,而是在进程内部提供队列服务的,偏向于取代ArrayBlockingQueue,因为这个阻塞队列是使用了锁来控制阻塞,关于并发其实有一些通用的最佳实践,就是用锁,即使是 JDK 提供的锁,也是比较耗资源的,当然这是跟不加锁的对比,同样是锁,JDK 的实现还是性能比较优秀的。常见的阻塞队列中例如 ArrayBlockingQueueLinkedBlockingQueue 都有锁的身影的存在,区别在于 ArrayBlockingQueue 是一把锁,后者是两把锁,不过重点不在几把锁,这里其实是两个问题,一个是所谓的 lock free, 对于一个单生产者的 disruptor 来说,因为写入是只有一个线程的,是可以不用加锁,多生产者的时候使用的是 cas 来获取对应的写入坑位,另一个是解决“伪共享”问题,后面可以详细点分析,先介绍下使用
首先是数据源

-
public class LongEvent {
-    private long value;
-
-    public void set(long value) {
-        this.value = value;
-    }
-
-    public long getValue() {
-        return value;
+    public long getValue() {
+        return value;
     }
 
     public void setValue(long value) {
@@ -1442,6 +1442,28 @@ Node *clone(Node *graph) {
         Disruptor
       
   
+  
+    Dubbo 使用的几个记忆点
+    /2022/04/02/Dubbo-%E4%BD%BF%E7%94%A8%E7%9A%84%E5%87%A0%E4%B8%AA%E8%AE%B0%E5%BF%86%E7%82%B9/
+    因为后台使用的 dubbo 作为 rpc 框架,并且会有一些日常使用情景有一些小的技巧,在这里做下记录作笔记用

+

dubbo 只拉取不注册

<dubbo:registry address="zookeeper://127.0.0.1:2181" register="false" />
+

就是只要 register="false" 就可以了,这样比如我们在开发环境想运行服务,但又不想让开发环境正常的请求调用到本地来,当然这不是唯一的方式,通过 dubbo 2.7 以上的 tag 路由也可以实现或者自行改造拉取和注册服务的逻辑,因为注册到注册中心的其实是一串带参数的 url,还是比较方便改造的。相反的就是只注册,不拉取

+

dubbo 只注册不拉取

<dubbo:registry address="zookeeper://127.0.0.1:2181" subscribe="false" />
+

这个使用场景就是如果我这个服务只作为 provider,没有任何调用其他的服务,其实就可以这么设置

+

权重配置

<dubbo:provider loadbalance="random" weight="50"/>
+

首先这是在使用了随机的负载均衡策略的时候可以进行配置,并且是对于多个 provider 的情况下,这样其实也可以部分解决上面的只拉取不注册的问题,我把自己的权重调成 0 或者很低

+]]>
+ + Java + Dubbo + + + Java + Dubbo + RPC + 负载均衡 + +
Filter, Interceptor, Aop, 啥, 啥, 啥? 这些都是啥? /2020/08/22/Filter-Intercepter-Aop-%E5%95%A5-%E5%95%A5-%E5%95%A5-%E8%BF%99%E4%BA%9B%E9%83%BD%E6%98%AF%E5%95%A5/ @@ -2598,100 +2620,6 @@ Node *clone(Node *graph) { Garbage-First Collector - - Dubbo 使用的几个记忆点 - /2022/04/02/Dubbo-%E4%BD%BF%E7%94%A8%E7%9A%84%E5%87%A0%E4%B8%AA%E8%AE%B0%E5%BF%86%E7%82%B9/ - 因为后台使用的 dubbo 作为 rpc 框架,并且会有一些日常使用情景有一些小的技巧,在这里做下记录作笔记用

-

dubbo 只拉取不注册

<dubbo:registry address="zookeeper://127.0.0.1:2181" register="false" />
-

就是只要 register="false" 就可以了,这样比如我们在开发环境想运行服务,但又不想让开发环境正常的请求调用到本地来,当然这不是唯一的方式,通过 dubbo 2.7 以上的 tag 路由也可以实现或者自行改造拉取和注册服务的逻辑,因为注册到注册中心的其实是一串带参数的 url,还是比较方便改造的。相反的就是只注册,不拉取

-

dubbo 只注册不拉取

<dubbo:registry address="zookeeper://127.0.0.1:2181" subscribe="false" />
-

这个使用场景就是如果我这个服务只作为 provider,没有任何调用其他的服务,其实就可以这么设置

-

权重配置

<dubbo:provider loadbalance="random" weight="50"/>
-

首先这是在使用了随机的负载均衡策略的时候可以进行配置,并且是对于多个 provider 的情况下,这样其实也可以部分解决上面的只拉取不注册的问题,我把自己的权重调成 0 或者很低

-]]>
- - Java - Dubbo - - - Java - Dubbo - RPC - 负载均衡 - -
- - Leetcode 021 合并两个有序链表 ( Merge Two Sorted Lists ) 题解分析 - /2021/10/07/Leetcode-021-%E5%90%88%E5%B9%B6%E4%B8%A4%E4%B8%AA%E6%9C%89%E5%BA%8F%E9%93%BE%E8%A1%A8-Merge-Two-Sorted-Lists-%E9%A2%98%E8%A7%A3%E5%88%86%E6%9E%90/ - 题目介绍

Merge two sorted linked lists and return it as a sorted list. The list should be made by splicing together the nodes of the first two lists.

-

将两个升序链表合并为一个新的 升序 链表并返回。新链表是通过拼接给定的两个链表的所有节点组成的。

-

示例 1

-
-

输入:l1 = [1,2,4], l2 = [1,3,4]
输出:[1,1,2,3,4,4]

-
-

示例 2

-

输入: l1 = [], l2 = []
输出: []

-
-

示例 3

-

输入: l1 = [], l2 = [0]
输出: [0]

-
-

简要分析

这题是 Easy 的,看着也挺简单,两个链表进行合并,就是比较下大小,可能将就点的话最好就在两个链表中原地合并

-

题解代码

public ListNode mergeTwoLists(ListNode l1, ListNode l2) {
-        // 下面两个if判断了入参的边界,如果其一为null,直接返回另一个就可以了
-        if (l1 == null) {
-            return l2;
-        }
-        if (l2 == null) {
-            return l1;
-        }
-        // new 一个合并后的头结点
-        ListNode merged = new ListNode();
-        // 这个是当前节点
-        ListNode current = merged;
-        // 一开始给这个while加了l1和l2不全为null的条件,后面想了下不需要
-        // 因为内部前两个if就是跳出条件
-        while (true) {
-            if (l1 == null) {
-                // 这里其实跟开头类似,只不过这里需要将l2剩余部分接到merged链表后面
-                // 所以不能是直接current = l2,这样就是把后面的直接丢了
-                current.val = l2.val;
-                current.next = l2.next;
-                break;
-            }
-            if (l2 == null) {
-                current.val = l1.val;
-                current.next = l1.next;
-                break;
-            }
-            // 这里是两个链表都不为空的时候,就比较下大小
-            if (l1.val < l2.val) {
-                current.val = l1.val;
-                l1 = l1.next;
-            } else {
-                current.val = l2.val;
-                l2 = l2.next;
-            }
-            // 这里是new个新的,其实也可以放在循环头上
-            current.next = new ListNode();
-            current = current.next;
-        }
-        current = null;
-        // 返回这个头结点
-        return merged;
-    }
- -

结果

-]]>
- - Java - leetcode - - - leetcode - java - 题解 - -
Leetcode 028 实现 strStr() ( Implement strStr() ) 题解分析 /2021/10/31/Leetcode-028-%E5%AE%9E%E7%8E%B0-strStr-Implement-strStr-%E9%A2%98%E8%A7%A3%E5%88%86%E6%9E%90/ @@ -2768,40 +2696,112 @@ Output: 0 - Leetcode 053 最大子序和 ( Maximum Subarray ) 题解分析 - /2021/11/28/Leetcode-053-%E6%9C%80%E5%A4%A7%E5%AD%90%E5%BA%8F%E5%92%8C-Maximum-Subarray-%E9%A2%98%E8%A7%A3%E5%88%86%E6%9E%90/ - 题目介绍

Given an integer array nums, find the contiguous subarray (containing at least one number) which has the largest sum and return its sum.

-

A subarray is a contiguous part of an array.

-

示例

Example 1:

+ Leetcode 021 合并两个有序链表 ( Merge Two Sorted Lists ) 题解分析 + /2021/10/07/Leetcode-021-%E5%90%88%E5%B9%B6%E4%B8%A4%E4%B8%AA%E6%9C%89%E5%BA%8F%E9%93%BE%E8%A1%A8-Merge-Two-Sorted-Lists-%E9%A2%98%E8%A7%A3%E5%88%86%E6%9E%90/ + 题目介绍

Merge two sorted linked lists and return it as a sorted list. The list should be made by splicing together the nodes of the first two lists.

+

将两个升序链表合并为一个新的 升序 链表并返回。新链表是通过拼接给定的两个链表的所有节点组成的。

+

示例 1

-

Input: nums = [-2,1,-3,4,-1,2,1,-5,4]
Output: 6
Explanation: [4,-1,2,1] has the largest sum = 6.

+

输入:l1 = [1,2,4], l2 = [1,3,4]
输出:[1,1,2,3,4,4]

-

Example 2:

-
-

Input: nums = [1]
Output: 1

+

示例 2

+

输入: l1 = [], l2 = []
输出: []

-

Example 3:

-
-

Input: nums = [5,4,-1,7,8]
Output: 23

+

示例 3

+

输入: l1 = [], l2 = [0]
输出: [0]

-

说起来这个题其实非常有渊源,大学数据结构的第一个题就是这个,而最佳的算法就是传说中的 online 算法,就是遍历一次就完了,最基本的做法就是记下来所有的连续子数组,然后求出最大的那个。

-

代码

public int maxSubArray(int[] nums) {
-        int max = nums[0];
-        int sum = nums[0];
-        for (int i = 1; i < nums.length; i++) {
-            // 这里最重要的就是这一行了,其实就是如果前面的 sum 是小于 0 的,那么就不需要前面的 sum,反正加上了还不如不加大
-            sum = Math.max(nums[i], sum + nums[i]);
-            // max 是用来承载最大值的
-            max = Math.max(max, sum);
+

简要分析

这题是 Easy 的,看着也挺简单,两个链表进行合并,就是比较下大小,可能将就点的话最好就在两个链表中原地合并

+

题解代码

public ListNode mergeTwoLists(ListNode l1, ListNode l2) {
+        // 下面两个if判断了入参的边界,如果其一为null,直接返回另一个就可以了
+        if (l1 == null) {
+            return l2;
         }
-        return max;
-    }
]]> - - Java - leetcode - - - leetcode + if (l2 == null) { + return l1; + } + // new 一个合并后的头结点 + ListNode merged = new ListNode(); + // 这个是当前节点 + ListNode current = merged; + // 一开始给这个while加了l1和l2不全为null的条件,后面想了下不需要 + // 因为内部前两个if就是跳出条件 + while (true) { + if (l1 == null) { + // 这里其实跟开头类似,只不过这里需要将l2剩余部分接到merged链表后面 + // 所以不能是直接current = l2,这样就是把后面的直接丢了 + current.val = l2.val; + current.next = l2.next; + break; + } + if (l2 == null) { + current.val = l1.val; + current.next = l1.next; + break; + } + // 这里是两个链表都不为空的时候,就比较下大小 + if (l1.val < l2.val) { + current.val = l1.val; + l1 = l1.next; + } else { + current.val = l2.val; + l2 = l2.next; + } + // 这里是new个新的,其实也可以放在循环头上 + current.next = new ListNode(); + current = current.next; + } + current = null; + // 返回这个头结点 + return merged; + }
+ +

结果

+]]> + + Java + leetcode + + + leetcode + java + 题解 + + + + Leetcode 053 最大子序和 ( Maximum Subarray ) 题解分析 + /2021/11/28/Leetcode-053-%E6%9C%80%E5%A4%A7%E5%AD%90%E5%BA%8F%E5%92%8C-Maximum-Subarray-%E9%A2%98%E8%A7%A3%E5%88%86%E6%9E%90/ + 题目介绍

Given an integer array nums, find the contiguous subarray (containing at least one number) which has the largest sum and return its sum.

+

A subarray is a contiguous part of an array.

+

示例

Example 1:

+
+

Input: nums = [-2,1,-3,4,-1,2,1,-5,4]
Output: 6
Explanation: [4,-1,2,1] has the largest sum = 6.

+
+

Example 2:

+
+

Input: nums = [1]
Output: 1

+
+

Example 3:

+
+

Input: nums = [5,4,-1,7,8]
Output: 23

+
+

说起来这个题其实非常有渊源,大学数据结构的第一个题就是这个,而最佳的算法就是传说中的 online 算法,就是遍历一次就完了,最基本的做法就是记下来所有的连续子数组,然后求出最大的那个。

+

代码

public int maxSubArray(int[] nums) {
+        int max = nums[0];
+        int sum = nums[0];
+        for (int i = 1; i < nums.length; i++) {
+            // 这里最重要的就是这一行了,其实就是如果前面的 sum 是小于 0 的,那么就不需要前面的 sum,反正加上了还不如不加大
+            sum = Math.max(nums[i], sum + nums[i]);
+            // max 是用来承载最大值的
+            max = Math.max(max, sum);
+        }
+        return max;
+    }
]]>
+ + Java + leetcode + + + leetcode java 题解 @@ -2924,6 +2924,77 @@ inorder = [9,3,15,20,7]
+ + Leetcode 1115 交替打印 FooBar ( Print FooBar Alternately *Medium* ) 题解分析 + /2022/05/01/Leetcode-1115-%E4%BA%A4%E6%9B%BF%E6%89%93%E5%8D%B0-FooBar-Print-FooBar-Alternately-Medium-%E9%A2%98%E8%A7%A3%E5%88%86%E6%9E%90/ + 无聊想去 roll 一题就看到了有并发题,就找到了这题,其实一眼看我的想法也是用信号量,但是用 condition 应该也是可以处理的,不过这类问题好像本地有点难调,因为它好像是抽取代码执行的,跟直观的逻辑比较不一样
Suppose you are given the following code:

+
class FooBar {
+  public void foo() {
+    for (int i = 0; i < n; i++) {
+      print("foo");
+    }
+  }
+
+  public void bar() {
+    for (int i = 0; i < n; i++) {
+      print("bar");
+    }
+  }
+}
+

The same instance of FooBar will be passed to two different threads:

+
    +
  • thread A will call foo(), while
  • +
  • thread B will call bar().
    Modify the given program to output "foobar" n times.
  • +
+

示例

Example 1:

+

Input: n = 1
Output: “foobar”
Explanation: There are two threads being fired asynchronously. One of them calls foo(), while the other calls bar().
“foobar” is being output 1 time.

+
+

Example 2:

+

Input: n = 2
Output: “foobarfoobar”
Explanation: “foobar” is being output 2 times.

+
+

题解

简析

其实用信号量是很直观的,就是让打印 foo 的线程先拥有信号量,打印后就等待,给 bar 信号量 + 1,然后 bar 线程运行打印消耗 bar 信号量,再给 foo 信号量 + 1

+

code

class FooBar {
+    
+    private final Semaphore foo = new Semaphore(1);
+    private final Semaphore bar = new Semaphore(0);
+    private int n;
+
+    public FooBar(int n) {
+        this.n = n;
+    }
+
+    public void foo(Runnable printFoo) throws InterruptedException {
+        
+        for (int i = 0; i < n; i++) {
+            foo.acquire();
+        	// printFoo.run() outputs "foo". Do not change or remove this line.
+        	printFoo.run();
+            bar.release();
+        }
+    }
+
+    public void bar(Runnable printBar) throws InterruptedException {
+        
+        for (int i = 0; i < n; i++) {
+            bar.acquire();
+            // printBar.run() outputs "bar". Do not change or remove this line.
+        	printBar.run();
+            foo.release();
+        }
+    }
+}
+]]>
+ + Java + leetcode + + + leetcode + java + 题解 + Print FooBar Alternately + +
Leetcode 121 买卖股票的最佳时机(Best Time to Buy and Sell Stock) 题解分析 /2021/03/14/Leetcode-121-%E4%B9%B0%E5%8D%96%E8%82%A1%E7%A5%A8%E7%9A%84%E6%9C%80%E4%BD%B3%E6%97%B6%E6%9C%BA-Best-Time-to-Buy-and-Sell-Stock-%E9%A2%98%E8%A7%A3%E5%88%86%E6%9E%90/ @@ -3026,6 +3097,87 @@ inorder = [9,3,15,20,7] + + Leetcode 1260 二维网格迁移 ( Shift 2D Grid *Easy* ) 题解分析 + /2022/07/22/Leetcode-1260-%E4%BA%8C%E7%BB%B4%E7%BD%91%E6%A0%BC%E8%BF%81%E7%A7%BB-Shift-2D-Grid-Easy-%E9%A2%98%E8%A7%A3%E5%88%86%E6%9E%90/ + 题目介绍

Given a 2D grid of size m x n and an integer k. You need to shift the grid k times.

+

In one shift operation:

+

Element at grid[i][j] moves to grid[i][j + 1].
Element at grid[i][n - 1] moves to grid[i + 1][0].
Element at grid[m - 1][n - 1] moves to grid[0][0].
Return the 2D grid after applying shift operation k times.

+

示例

Example 1:

+
+

Input: grid = [[1,2,3],[4,5,6],[7,8,9]], k = 1
Output: [[9,1,2],[3,4,5],[6,7,8]]

+
+

Example 2:

+
+

Input: grid = [[3,8,1,9],[19,7,2,5],[4,6,11,10],[12,0,21,13]], k = 4
Output: [[12,0,21,13],[3,8,1,9],[19,7,2,5],[4,6,11,10]]

+
+

Example 3:

+

Input: grid = [[1,2,3],[4,5,6],[7,8,9]], k = 9
Output: [[1,2,3],[4,5,6],[7,8,9]]

+
+

提示

    +
  • m == grid.length
  • +
  • n == grid[i].length
  • +
  • 1 <= m <= 50
  • +
  • 1 <= n <= 50
  • +
  • -1000 <= grid[i][j] <= 1000
  • +
  • 0 <= k <= 100
  • +
+

解析

这个题主要是矩阵或者说数组的操作,并且题目要返回的是个 List,所以也不用原地操作,只需要找对位置就可以了,k 是多少就相当于让这个二维数组头尾衔接移动 k 个元素

+

代码

public List<List<Integer>> shiftGrid(int[][] grid, int k) {
+        // 行数
+        int m = grid.length;
+        // 列数
+        int n = grid[0].length;
+        // 偏移值,取下模
+        k = k % (m * n);
+        // 反向取下数量,因为我打算直接从头填充新的矩阵
+        /*
+         *    比如
+         *    1 2 3
+         *    4 5 6
+         *    7 8 9
+         *    需要变成
+         *    9 1 2
+         *    3 4 5
+         *    6 7 8
+         *    就要从 9 开始填充
+         */
+        int reverseK = m * n - k;
+        List<List<Integer>> matrix = new ArrayList<>();
+        // 这类就是两层循环
+        for (int i = 0; i < m; i++) {
+            List<Integer> line = new ArrayList<>();
+            for (int j = 0; j < n; j++) {
+                // 数量会随着循环迭代增长, 确认是第几个
+                int currentNum = reverseK + i * n +  (j + 1);
+                // 这里处理下到达矩阵末尾后减掉 m * n
+                if (currentNum > m * n) {
+                    currentNum -= m * n;
+                }
+                // 根据矩阵列数 n 算出在原来矩阵的位置
+                int last = (currentNum - 1) % n;
+                int passLine = (currentNum - 1) / n;
+
+                line.add(grid[passLine][last]);
+            }
+            matrix.add(line);
+        }
+        return matrix;
+    }
+ +

结果数据


比较慢

+]]>
+ + Java + leetcode + + + leetcode + java + 题解 + Shift 2D Grid + +
Leetcode 155 最小栈(Min Stack) 题解分析 /2020/12/06/Leetcode-155-%E6%9C%80%E5%B0%8F%E6%A0%88-Min-Stack-%E9%A2%98%E8%A7%A3%E5%88%86%E6%9E%90/ @@ -3114,24 +3266,93 @@ minStack.getMin(); // return -2