跳至主要内容

博文

目前显示的是 一月, 2020的博文

Thread 之 join() 方法

今儿实际写项目的时候,发现某个操作在多线程下,怎么也得不到正确的结果,明明已经对变量使用原子类了。
折腾半天,找到了答案:主线程未拿到子线程应该返回的正确的值,换句话说,子线程操作是没问题的,但是主线程的操作在子线程完成之前就执行了,没有拿到正确的值。
解决办法:用上 join() 方法,阻塞主线程,等待子线程完成后,再继续执行之后的代码。
参考了以下博客:
Thread之三:Thread Join()的用法
Java多线程中join方法的理解

Exception in thread "1" java.lang.IllegalMonitorStateException

把玩同步锁时,抛出了一个异常Exception in thread "1" java.lang.IllegalMonitorStateException:

抛出异常的代码如下:

根据异常类型,在ReentrantLock代码注释找到了答案:如果当前线程不是此锁的持有者则抛出IllegalMonitorStateException异常。 然后我在代码中加了跟踪语句: 但是还是没理解为什么上个线程执行完了没有释放锁,mark一下,日后再看。



我知道原因了,我真的是太傻了。
这串代码执行结果并不是没有释放锁,而是因为释放了锁没有重新上锁,导致再次释放锁时找不到锁而抛出异常。这也就是为什么执行结果是抛出异常后就终止运行,因为Lock锁如果尝试获取不到锁,线程可以不用一直等待就结束了。

反转链表的几种写法

今儿重写反转链表题时,写法和上次不一样,并且看了一下提交记录,竟然写了四种写法了,试着记录一下。
起初是刚学链表时写的,先存下节点,费时费空间:
然后学会了双指针迭代:
再后来试着用递归改写,思路还是双指针:
这次写的递归,emmm,觉得没上次看起来舒服,还是直接看代码吧:

Mike Shinoda - In The End [Live at Reading Festival 2018] [60fps]

记一次HashSet的错误使用

写leetcode 804时,使用了HashSet,怎么也得不到正确的结果,debug得知,集合中放进去了所有的元素,并未去重,纳闷了。
回想起HashSet的检测重复机制才想起来被存放元素的类需要重写hashCode()和equals(),一看StringBuilder源码里果然没有重写,最后还是使用了HashSet,set.add(stringBuilder.toString())。

Spring Boot2中ApplicationRunner的优先级

用拦截器实现访问统计的时候,需要从数据库初始化变量pv,但是pv不能放在拦截器构造方法里,那怎么让他只执行一次初始化?
之前写博客程序的时候使用过ApplicationRunner接口,用在程序每次启动时校验数据库数据是否完整,这次依然这么实现,重写run()方法,然后从数据库拿数据初始化pv。
但是使用的时候开始疑惑了,这货的优先级是咋样的?是先执行拦截器的方法,还是先执行拦截器里的注入,又还是先执行run()?我看文档没整明白,还是测试吧;

新建一测试Bean A:
然后在拦截器里面自动注入A,再令拦截器实现ApplicationRunner接口并重写Run方法,拦截器内基本代码如下:
运行后的测试结果为: 其实关于这个结果我有一点想不明白,既然会先执行自动注入,再是执行拦截器的构造函数,那么为什么不能在构造函数中执行pv初始化呢?
待更...

HandlerInterceptor和HandlerInceptorAdaptor

突然纠结这俩接口,主要是因为WebMvcConfigurer和WebMvcConfigurerAdapter。我是这么想的,前者为Spring Boot2的推荐使用,后者在Spring Boot2被标记为deprecated,但是WebMvcConfigurerAdapter是基于WebMvcConfigurer实现的呀,常规思路不都是推荐使用最新的嘛,怎么这里往回走了(其实源码注释已经给了解释,并且基于这个解释也能简单从一个方面说明Spring Boot2为啥最低只支持JDK8)。

然后在使用HandlerInceptorAdaptor的时候就开始纠结了,咋这儿就推荐使用HandlerInceptorAdaptor了?
看了一下HandlerInceptorAdaptor源码,注释说是HandlerInterceptor的精简版本,HandlerInceptorAdaptor确实是实现了AsyncHandlerInterceptor,并且AsyncHandlerInterceptor继承自HandlerInterceptor。

总而言之,怪我这思想太简单,不能简单从命名(Adaptor)去判断使用哪个。

更多可参考stackoverflow:

What is difference between HandlerInterceptor and HandlerInceptorAdaptor

HashSet与TreeSet

HashSet与TreeSet都是常见的集合类,均为非同步。

简单看了看HashSet与TreeSet的源码,HashSet代码加上注释不到400行,TreeSet代码加上注释不到600行,为啥这么少的代码?细看发现是因为内部分别使用了HashMap和TreeMap,并且直接使用了它们的的原生方法。
故可得知HashSet基于哈希表+红黑树,适合CUD,碰撞理想情况下O(n),并且不保证有序;TreeSet基于红黑树(自平衡二叉搜索树),适合R,但是CUD相对较慢O(log(n))(自平衡消耗时间)。

那么具体是咋实现的?一个是键值对形式一个是线性表呀?
拿TreeSet来说,内部默认使用TreeMap实现,创建了一Dummy对象“PRESENT”(见101行声明及注释)作为value,然后所有元素以key的形式扔入了TreeMap里面(见254行add(E e)方法)。HashSet也是如此形式(可参见源码99行与219行),好像套娃🤣。

HashSet还有一个子类叫做LinkedHashSet,内部实现了一个可以记录插入顺序的双向链表,故支持按照插入顺序遍历,并且继承了HashSet的特性——支持O(n)的CUD操作,但由于需要维护链表的开销导致其性能略低于HashSet。(参见注释)




HashMap源码学习路线指北

好多此类文章讲解源码的时候不标注JDK版本,看着觉得很危险,更加觉得意犹未尽,还是我自己看看源码得了。

JDK最近的两个LTS版本为JDK8 & JDK11,所以我选择这俩版本的源码进行学习,但是相比之下两个版本HashMap的代码,JDK11只比JDK8多了注释,故本文基于JDK8,记录下源码学习的路线。

代码这么长上来直接撸代码的话肯定会稀里糊涂,得站在巨人的肩膀上学习。代码里面注释写得挺详细,开篇就是100行的代码注释,介绍HashMap。注释一定要看啊!

众所周知HashMap底层是数组和链表组合构成的数据结构,见396行。其内部实现见279行,有四个变量:hash值、key值、value值、链表指针next。
236行开始定义的几个静态变量非常重要,一定得看。


理解这些再去看源码会容易很多,另外关于Hashtable,类注释中已经声明建议使用HashMap或者ConcurrentHashMap替代,简单看了一下源码,其大体和HashMap相似,但是其部分具体实现细节还是和HashMap有蛮大出入的。

WSL暴力换源

开发需要,装了个WSL(终于是正途使用WSL了😂);
由于国内网络环境不友好,导致拉取官方源速度极慢,便准备换源,但是自带的vi有bug,编辑操作会出现乱码,最简单的解决办法就是安装vim,但是官方源速度极慢,这时先有鸡还是先有蛋的问题出现了;

首先执行 cat /etc/debian_version 查看当前版本,然后找个合适的软件源镜像,我常用的有 阿里云镜像 和 清华大学开源软件镜像站 ,由于我的当前版本是Debian 10,阿里云镜像并未提供,便选择了后者,就像之前说的,vi无法正常编辑咋整呢,当然是直接在win10中编辑啦,wsl的根目录在win10中路径为:
C:\Users\[username]\AppData\Local\Packages\[TheDebianProject.DebianGNULinux_76v4gfsz19hv4]\LocalState\rootfs 然后找个编辑器根据镜像源给的提示编辑/etc/apt/sources.list,PS:把https改为http能省不少麻烦。

解决MDL框架中show-dialog只响应第一次调用

MDL框架中使用show-dialog组件时,只会响应第一次调用的地儿,但是这在BootStrap中并不会出现。摸索后终于弄清楚了是怎么解决。

假设当前有俩button,都需要唤起同一个dialog组件,则需要为button设置不同的id,并在js中动态传递id进行绑定,方可解决上述问题;

Demo:



后端设置Thymeleaf前台全局变量

场景:应用使用 Spring Boot + Thymeleaf 开发,页面分成了多个 Thymeleaf 视图,现在要实现传递一个参数至整个前台,让每个 Thymeleaf 视图都能接收到该参数;

思路:翻阅文档未果,便想着在每个访问控制方法里手动传值,但是这样太麻烦而且代码冗余难看,多番搜寻,终于在 Github 他人的项目文件中找到了实现方法;

实现

// 很好的体现了JVM的核心思想,”一次编写,随处运行“,哈哈哈;
@Resourceprivate void configureThymeleafStaticVars(ThymeleafViewResolver viewResolver) {
    viewResolver.addStaticVariable("avatarUrl", adminService.getAdmin().getAvatar_url());
}