震惊!我竟然发现了JDK源码的问题
读源码时的思考
最近在看concurrent包下线程池的源码,当我看到ThreadPoolExecutor类的时候,发现了JDK源码的一个问题。以下是ThreadPoolExecutor类的addWorker方法的代码片段:
boolean workerStarted = false;
boolean workerAdded = false;
Worker w = null;
try {
w = new Worker(firstTask);
final Thread t = w.thread;
if (t != null) {
final ReentrantLock mainLock = this.mainLock;
mainLock.lock();
try {
int rs = runStateOf(ctl.get());
if (rs < SHUTDOWN ||
(rs == SHUTDOWN && firstTask == null)) {
if (t.isAlive()) // precheck that t is startable
throw new IllegalThreadStateException();
workers.add(w);
int s = workers.size();
if (s > largestPoolSize)
largestPoolSize = s;
workerAdded = true;
}
} finally {
mainLock.unlock();
}
if (workerAdded) {
t.start();
workerStarted = true;
}
}
} finally {
if (! workerStarted)
addWorkerFailed(w);
}
return workerStarted;
这段代码的功能是完全没有问题的,但是如果使用卫语句,代码的可读性就会更高了。那么什么是卫语句呢?
欢迎关注微信公众号:万猫学社,每周一分享Java技术干货。
什么是卫语句?
条件表达式通常有两种表现形式,第一种形式是:所有分支都属于正常行为;第二种形式则是:条件表达式提供的答案中只有一种是正常行为,其他都是不常见的情况。这两类条件表达式有不同的用途,这一点应该通过代码表现出来。
如果两条分支都是正常行为,就应该使用形如if...else...的条件表达式;如果某个条件极其罕见,就应该单独检查该条件,并在该条件为真时立刻从函数中返回。这样的单独检查常常被称为“卫语句”(guard clauses)。只看概念干巴巴的,不好理解,我们来举两个例子。
欢迎关注微信公众号:万猫学社,每周一分享Java技术干货。
条件检查替换
这是一个计算员工薪资的方法,其中以特殊规则处理驻外员工和退休员工的薪资。这些情况不常有,但的确会偶尔出现。
public double getSalary() {
double result;
if (this.isSeparated) {//驻外员工
result = this.separatedSalary();
} else {
if (this.isRetired) {//退休员工
result = this.retiredSalary();
} else {//正常员工
result = this.normalSalary();
}
}
return result;
}
这段代码中,非正常情况的检查掩盖了正常情况的检查,所以应该用卫语句来取代这些条件检查,以提高程序清晰度。对于每个检查,放进一个卫语句。卫语句要不就从函数中返回,要不就抛出一个异常。
public double getSalary() {
if (this.isSeparated) {//卫语句
return this.separatedSalary();
}
if (this.isRetired) {//卫语句
return this.retiredSalary();
}
return this.normalSalary();
}
欢迎关注微信公众号:万猫学社,每周一分享Java技术干货。
反转条件替换
这是一个已知长宽高求长方体体积的方法,但有个特殊的需求:高大于0时,打印万猫学社
。(惊不惊喜?意不意外?突不突兀?变不变态?是的,有时候我们接到的需求就是这样的。)代码是这样的:
public double getVolume(double length, double width, double height) {
double result = 0.0;
if (height > 0.0) {
System.out.println("万猫学社");
if (length > 0.0 && width > 0.0) {
result = length * width * height;
}
}
return result;
}
还是用卫语句替换条件检查,但是我们需要将相应的条件反转过来,也就是做逻辑非运算。
public double getVolume(double length, double width, double height) {
if (height <= 0.0) {//卫语句,!(height > 0)
return 0.0;
}
System.out.println("万猫学社");
if (length <= 0.0 || width <= 0.0) {//卫语句,!(length > 0 && width > 0)
return 0.0;
}
return length * width * height;
}
欢迎关注微信公众号:万猫学社,每周一分享Java技术干货。
为什么要使用卫语句?
卫语句的精髓是:给某一条分支以特别的重视。如果使用if...else...结构,你对if分支和else分支的重视是同等的。这样的代码结构传递给阅读者的消息就是:各个分支有同样的重要性。
卫语句就不同了,它告诉阅读者:“这种情况很罕见,如果它真的发生了,请做一些必要的整理工作,然后退出。”如果对方法剩余部分不再有兴趣,当然应该立刻退出。引导代码的阅读者去看一个没有用的else区段,只会妨碍他们的理解。用了卫语句以后,代码更容易被理解,被维护。
欢迎关注微信公众号:万猫学社,每周一分享Java技术干货。
优化JDK代码
看了上面的讲解,addWorker方法的代码片段就可以优化为:
boolean workerStarted = false;
boolean workerAdded = false;
Worker w = null;
try {
w = new Worker(firstTask);
final Thread t = w.thread;
if (t == null) {//卫语句
return workerStarted;
}
final ReentrantLock mainLock = this.mainLock;
mainLock.lock();
try {
int rs = runStateOf(ctl.get());
//卫语句
if (rs > SHUTDOWN || (rs == SHUTDOWN && firstTask != null)) {
return workerStarted;
}
if (t.isAlive())// precheck that t is startable
throw new IllegalThreadStateException();
workers.add(w);
int s = workers.size();
if (s > largestPoolSize)
largestPoolSize = s;
workerAdded = true;
} finally {
mainLock.unlock();
}
if (workerAdded) {
t.start();
workerStarted = true;
}
} finally {
if (!workerStarted)
addWorkerFailed(w);
}
return workerStarted;
修改后的代码,理解起来是不是更容易了?假如再加入新的功能,可以更容易修改代码。
欢迎关注微信公众号:万猫学社,每周一分享Java技术干货。
结语
这段JDK源码在功能上没有任何问题,架构设计也堪称完美,不过我认为在可读性上还是可以优化的。类似这样的嵌套条件表达式的代码在JDK源码中不止这一处,可能是作者当时没有考虑到使用卫语句,或者没有像我这样的吹毛求疵。希望大家在自己编码过程中,也可以尽量使用卫语句取代嵌套条件表达式,以提高代码的清晰度和可维护性。
欢迎关注微信公众号:万猫学社,每周一分享Java技术干货。
参考文献:《重构:改善既有代码的设计》
震惊!我竟然发现了JDK源码的问题的更多相关文章
- 调试过程中发现按f5无法走进jdk源码
debug 模式 ,在fis=new FileInputStream(file); 行打断点 调试过程中发现按f5无法走进jdk源码 package com.lzl.spring.test; impo ...
- 重新编译jdk源码,启用debug信息
我有一个不知道是好还是不好的习惯,搞不懂的一些玩意儿,喜欢调试然后单步执行看这玩意儿到底是怎么运行的. 今天看到正则表达式的时候,appendReplacement()这个方法怎么也看不明白它是怎么工 ...
- eclipse下导入jdk源码
一直想好好看看jdk的源码,虽然可以直接解压jdk下的src看,但是终究不方便!后来发现可以导入到eclipse中,就在网上找了一些方法,下面就和大家分共享: step1:打开eclipse选择Win ...
- Timer的故事----Jdk源码解读
咱们今天也来说说定时器Timer Timer是什么? Timer n. [电子] 定时器:计时器:计时员 从翻译来看,我们可以知道Timer的本意是,定时定点. 而JDK中Timer类也的确是这个本 ...
- JDK源码包结构分类
最近查看JDK源码时,无意间发现几个类在陌生包里:com.sun.*.sun.*.org.*,google了一把总结了下以备他人搜索,如内容有误欢迎指正! Jre库包含的jar文件(jdk1.6) ...
- jdk源码调试功能
JDK源码重新编译——支持eclipse调试JDK源码--转载 最近在研究jdk源码,发现debug时无法查看源码里的变量值. 因为sun提供的jdk并不能查看运行中的局部变量,需要重新编译一下rt. ...
- JDK源码重新编译——支持eclipse调试JDK源码--转载
最近在研究jdk源码,发现debug时无法查看源码里的变量值. 因为sun提供的jdk并不能查看运行中的局部变量,需要重新编译一下rt.jar. 下面这六步是编译jdk的具体步骤: Step 1: ...
- JDK源码学习--String篇(二) 关于String采用final修饰的思考
JDK源码学习String篇中,有一处错误,String类用final[不能被改变的]修饰,而我却写成静态的,感谢CTO-淼淼的指正. 风一样的码农提出的String为何采用final的设计,阅读JD ...
- JDK源码分析—— ArrayBlockingQueue 和 LinkedBlockingQueue
JDK源码分析—— ArrayBlockingQueue 和 LinkedBlockingQueue 目的:本文通过分析JDK源码来对比ArrayBlockingQueue 和LinkedBlocki ...
随机推荐
- Tomcat8 结构原理解析
Tomcat是JavaWeb组件架构中一款apache开源的服务器软件,通过对其的学习,总结并且分享了关于它的知识,下边是分享ppt内容,希望对想了解tomcat人有帮助. Tomcat历史 1999 ...
- 并发编程之原子操作Atomic&Unsafe
原子操作:不能被分割(中断)的一个或一系列操作叫原子操作. 原子操作Atomic主要有12个类,4种类型的原子更新方式,原子更新基本类型,原子更新数组,原子更新字段,原子更新引用.Atomic包中的类 ...
- 网络游戏开发-客户端4 关于Egret的本地坐标和舞台坐标
因为最近公司事情比较多,所以没怎么更新博客. 不过咱们这个游戏还是在继续往下写. 今天晚上打算写斗地主的出牌动画,遇到一个问题,就是关于本地坐标和舞台坐标的计算问题 在Egret官网的解释是:x 和 ...
- Python 元组(Tuple)操作详解
Python的元组与列表类似,不同之处在于元组的元素不能修改,元组使用小括号, 列表使用方括号,元组创建很简单,只需要在括号中添加元素,并使用逗号隔开即可 一.创建元组 代码如下: tup1 = (' ...
- python编程基础之十三
列表的通用操作: list1 = [43, 65, 76, 6] list2 = [45, 77, 90, 11, 2, 4, 66] print(list1+ list2) # 列表组合 prin ...
- mysql锁表处理语句
show OPEN TABLES where In_use > 0; -- 查询是否锁表show processlist; -- 查询到相对应的进程===然后killidSELECT * FRO ...
- oracle之新建用户与授权
1.登录,口令为Oracle12c 2.新建用户 3.口令自己设置 4.按下图给角色授权,点击用用 5.登录刚刚创建的用户
- 从零起步 系统入门Python爬虫工程师 ✌✌
从零起步 系统入门Python爬虫工程师 (一个人学习或许会很枯燥,但是寻找更多志同道合的朋友一起,学习将会变得更加有意义✌✌) 大数据时代,python爬虫工程师人才猛增,本课程专为爬虫工程师打造, ...
- Linxu下Yii2的POST请求被拒经历
Linxu下Yii2的POST提交被拒经历 介于对Yii2的使用,浅谈一下自己的经验,在以往的项目中我使用的框架是Yii1,由于Yii2的出现,所以极力的想使用一下它的新特性. 我的使用环境Linux ...
- Spring Cloud Gateway的动态路由怎样做?集成Nacos实现很简单
一.说明 网关的核心概念就是路由配置和路由规则,而作为所有请求流量的入口,在实际生产环境中为了保证高可靠和高可用,是尽量要避免重启的,所以实现动态路由是非常有必要的:本文主要介绍 Spring Clo ...