CompletableFuture 的并发性能研究

今天继续探讨 CompletableFuture 的特性,它并发时的性能如何呢?我们知道集合的 stream() 后的操作是序列化进行的,parallelStream()是能够并发执行的,而用 CompletableFuture 可以更灵活的控制并发。

我们先可以对比一下 parallelStream() 与 CompletableFuture 的性能差异

假设一个这样的耗时 1000 毫秒的计算任务

分别用下面两个方法来测试,任务数可以通过参数来控制 阅读全文 >>

理解 CompletableFuture 的任务与回调函数的线程

继续对 CompletableFuture 的学习,本然依然不对它的众多方法的介绍,其实也不容易通过一篇述说完所有 CompletableFuture 的操作。此处重点了解下 CompletableFuture 几类操作时所使用的线程,CompletableFuture 的方法重点在它的静态方法以及实现自 CompletionStage 接口的方法,如果是意图异步化编程,反而自我标榜的 Future 中的方法用的少了。

CompletableFuture 根据任务的主从关系为

  1. 提交任务的方法,如静态方法 supplyAsync(supplier[, executor]),  runAsync(runnable[, executor])
  2. 回调函数,即对任务执行后所作出回应的方法,多数方法了,如 thenRun(action), thenRunAsync(action[, executor]), whenComplete(action), whenCompleteAsync(action[, executor]) 等

根据执行方法可分为同步与异步方法,任务都是要被异步执行,所以提交任务的方法都是异步的。而对任务作出回应的方法很多分为两个版本,如

  1. 同步方法,如 thenRun(action), whenComplete(action)
  2. 异步方法,如 thenRunAsync(action[, executor]), whenCompleteAsync(action[, executor]), 异步方法可以传入线程池,否则用默认的

因此所要理解的 CompletableFuture 的线程会涉及到任务与回调函数所使用的线程。 阅读全文 >>

Java 8 CompletableFuture 浅入

Java 1.5 有了 Future, 可谓是跨了一大步,继而 Java 1.8 新加入一个 Future 的实现 CompletableFuture, 从此线程与线程之间可以愉快的对话了。最初两个线程间的协调我采用过 Object 的  wait() 和 notify() , Thread 的 join() 方法,那可算是很低级的 API 了,是否很多 Java 程序都不知道它们的存在,或根本没用过它们。

如果是简单的等待所有线程完成可使用 Java 1.5 的 CountDownLatch, 这里有一篇介绍 CountDownLatch 协调线程, 就是实现的 waitAll(threads) 功能。而 Java 8 的 CompletableFuture 的功能就多去,可简单使用它实现异步方法。虽说 CompletableFuture 实现了 Future 接口,但它多数方法源自于 CompletionStage, 所以还里氏代换,用 Future 来引用 CompletableFuture 实例就很牵强了; 这也是为什么 PlayFramework 自 2.5 开始直接暴露的类型是 CompletionStage 而非其他两个。

顾名思义,CompletableFuture 代表着一个 Future 完成后该干点什么,具体大致有:

  1. Future 完成后执行动作,或求取下一个 Future 的值。then...
  2. 多个 Future 的协调; 同时完成该怎么,其中一个完成该如何。allOf, anyOf

有时候可以把 Future 想像成与线程是一一对应的。 阅读全文 >>

巩固 Java Future 的使用

Future 还是一 Java 1.5 带进来的产物,但过去那么多年实际代码中却很少有直接接触, 大约它多是隐匿在各种现成框架中默默的为我们服务。Future 本身不代表着多线程,而是代表着需异步计算的结果, 将来的一个期待,至于后来真正的结果不可知。在此之前想要获得一个 Runnable 在其他线程中的计算结果颇费波折,有了 Future 加之它身后的 Callable 一切就变得简单了。

对比一下 Java 1.5 前后的下面几个概念

  1. Callable 相当于之前的 Runnable, 只是 Callable 是有返回值的
  2. ExecuteService.submit(callable): Future 就类似于之前的 Thread(runnable)
    只是前者 submit 后立即执行,通过 get() 获得结果,后者用 start() 方法启动,runnable 是没有结果的。如果你也不想关心 Future 的结果也能 ExecuteService.submit(runnable)

只有 callable 被提交(到线程池) 后返回的 Future 才可能会有结果, 所以下面的代码永远等不到结果

Future<String> future = new FutureTask<>(() -> "Never");
String result = future.get();

最容易理解的 Future 基本使用代码如下: 阅读全文 >>

Hibernate 映射枚举(Enum) 类型的属性

在数据库中我们一般用整数或字符串来表示枚举值(有些数据库(如 MySQL)本身带有枚举类型), 而在使用 Hibernate 时实体对象中也用 Integer 或 String 来表示枚举就不那么友好了。试想来我们这样定义实体对象的两个属性

@Entity
public class User {
  ....
  public Integer type;  //0: Individual 类型,1: Company 类型
  public String gender;  //可取值 Male 和 Female
}

这样的定义很不严谨,type 和 gender 理论上可取任何值,这会造成表中数据的混乱。其实 Hibernate 在 Java 实体对象中是可以直接用枚举类型与数据库中的整数或字符串映射,需用到 @Enumerated 注解,用法如下: 阅读全文 >>

Java 的参数检查与断言 - Guava Preconditions

在前一篇 Scala 的参数检查与断言: require, assert, assume 和 ensuring,捉摸 Scala 的断言时提到了 JDK 内置对断言的粗略支持,也就是 assert 语句,并且默认该特性是被关掉,需 -ea 开启。

assert object != null;
assert object != null : "object can't be null";

还进一步接触了 Scala 的 Predef 方法 require, assert, assume, 和 ensuring 是怎么检验参数与断言运算结果的,Scala 的这些方法在校验失败时相应的抛出 IllegalArgumentException 和  AssertionError 异常。

JDK  7 引入了 Objects 工具类,它的三个 T requireNotNull(T object) 方法能对参数进行 null 值检查,null 时抛出 NullPointerException

阅读全文 >>

Scala 的参数检查与断言: require, assert, assume 和 ensuring

似乎 C/C++ 的编程人员相比于 Java 更偏爱于断言,JDK 1.4 才开始引入 assert 的支持,但默认是关闭的,需要用 -ea 编译选项打开,否则代码中的 assert 语句全被忽略,一般会在单元测试中开启该选项。简单回顾一下 JDK 自带的断言,它用两种写法

assert object != null;
assert object != null : "object can't be null";

第一个参数是个 bool 值,断言失败只会笼统的抛出java.lang.AssertionError 异常,并不区分是在检验方法参数还是中间运算结果。严谨来说我们会希望参数检查不通过时抛出 java.lang.IllegalArgumentException; 而中间运算结果的断言不过希望抛出 java.lang.AssertionError, 最好是 java.lang.IllegalStateException

很多时候我们不会去使用 -ea 编译选项,也就是主动放弃了 JDK 本身的断言功能。介于两个因素(不同的断言错误和默认的断言选项关闭),Scala 为我们提供了更方便的参数检查与断言方法,它们来自于 Predef, 其所定义的方法可以直接使用

阅读全文 >>

SQL Server 和 HSQLDB 中使用 merge into 完成 saveOrUpdate 操作

当我们调用 Hibernate 的  saveOrUpdate() 或 JPA 的 save() 方法的 Hibernate 实现时,都会做两步操作:1)按 ID 查询记录是否已存在,2)不存在插入新记录,存在则更新原记录。这种两步操作其实可以在 SQL Server 和 HSQLDB 中一条语句完成,这就是本文要介绍的 merge into 语句。感觉到用数据库自己的特性,并且一条语句会比 saveOrUpdate() 两步操作性能要好,还需实测。

之所以把 SQL Server 和 HSQLDB 扯到一块来讲,是因为我们在实际项目中的单元测试是基于 HSQLDB 内存数据库的。merge into 如其名所示,它应该是给予我们便利的去根据把一个表中符合条件的记录合并到另一个表中去。我们这里只利用它的这特性去实现类似 Hibernate 的 saveOrUpdate() 操作。

假设我们有一个简单的表

如果指 id 的记录已存在更新原来记录的 name 和  address, 不存在则插入新记录 阅读全文 >>

Spring 如何初始化泛型类实例

在 Java 中对于泛型类型,比如这样简单的类定义

class Processor<T> {}

如果直接初始化时要指定具体类型的话,我们可以这么写

Processor<String> processor = new Processor<>();  //Java 7 及以上版本

Spring 对基本泛型的初始化

如果我们要用 Spring 容器来初始化这个类,比如给上面那个类加个 @Named 注解

@Named
class Processor<T> {
}

这时候我们通过 beanFactory.getBean(Processor.class) 得到的是一个什么样的实例呢?Spring 怎么知道要指定什么具体类型呢?很简单,任何不确定的情况都是 Object。所以通过容器得到的  Processor 实例相当于用下面代码构造出来的

Processor processor = new Processor();  //更准确来讲是 Processor<Object> processor = new Processor<>();

再进一步,对于有上限约束的泛型定义,Spring 才如何应对呢?像 阅读全文 >>

Spring 项目中把 SQL 语句写在 .sql 文件中

我们在使用 JDBC 时, 如果把所有的 SQL 语句全写在 Java 文件中, 由于 Java 不支持 Here Document, 多行字符串要么用加号, 要么用 Java 8 的 String.join() 方法来连接, 同时不能对 SQL 语句进行语法加亮, 所以这样的 SQL 字符串阅读性很差. 别说为何不用 Hibernate 之类的而不直接写原始的 SQL 语句, 在操作复杂的系统时还是会用到 JdbcTemplate 吧.

所以我们希望能把 SQL 语句写在单独的 *.sql 文件里, 这样很多编辑器就能语法高亮显示, 或在输入时还能得到智能提示. 有种办法是把 *.sql 用作为属性文件, 那么在其中定义多行的 SQL 语句时就得这样

select.user=select id, firstname, lastname, address \
  from users \
  where id=?

加载后就能用 getProperty("select.user") 来引用相应的语句了. 属性文件的换行与 Bash  一样, 也是用  \, 但如此, 则 *.sql 并非一个纯粹的 SQL 文件, 不能正确的进行语法加亮, 一旦写上 SQL 的注释 -- 就更是在添乱了.

所以我们的第二个方案是: 首先 *.sql 就该是一个真正的  SQL 文件, 而不是伪装的属性文件, 为了能在程序中引用每一条 SQL 语句, 我们该如何表示各自的 Key 呢? 这里的灵感仍然是来自于 Linux Shell, 在 Linux Shell 中指定执行环境的用了特殊的注释方式 #!, 如

#!/bin/bash
#!/usr/bin/env python

阅读全文 >>