前面我们提到线程池处理批量接口请求实践但是在语法上比较复杂,还需要进行线程间的同步,也需要一定的Java知识,最近在学习Golang语言时,感觉go关键字十分高效,只要是想异步执行的方法,只需在前面添加go关键字即可。
如果Java也能实现一个类似go的关键字,那该多好啊!
思路
Java本身也是支持闭包的,通过闭包重建一个java.lang.Runnable
的匿名实现类,然后创建线程去执行对应的方法,应该是可以实现简单异步功能。
既然Java都能支持,那Groovy肯定也有解决方案了,至少可以直接用Java的方案。
几种语言的闭包使用可以参考
实践
思路比较简单,下面分享一下实践过程。
Java
不同于其他语言,Java闭包方法根据不同的参数、返回值有不同的实现类。这个地方有点烦,不够灵活。我试了java.util.function
下面的多个实现类,最终选择了java.util.function.Supplier
,原因是这个实现类没有参数,但是需要一个返回值。
There is no requirement that a new or distinct result be returned each time the supplier is invoked.
代码和使用方式如下:
package com.funtest.javatest;
import com.funtester.frame.SourceCode;
import java.util.function.Supplier;
public class Sync extends SourceCode {
public static void main(String[] args) {
sync(() -> {
sleep(0.1);
output("tester");
return DEFAULT_CHARSET;
});
output("FunTester");
}
public static void sync(Supplier f) {
new Thread(() -> {
f.get();
}).start();
}
}
控制台输出:
INFO-> main 当前用户:oker,工作目录:/Users/oker/IdeaProjects/funtester/,系统编码格式:UTF-8,系统Mac OS X版本:10.16
INFO-> main FunTester
INFO-> Thread-1 tester
Process finished with exit code 0
这里先不展示Groovy如何使用了。后面有更精彩的。
Groovy
Groovy语法相当简单,用一个groovy.lang.Closure
解决所有问题。
代码和使用方式如下:
import com.funtester.frame.SourceCode
class Sync extends SourceCode {
public static void main(String[] args) {
sync {
sleep(0.2)
output(320)
}
output("FunTester")
}
static void sync(Closure f) {
new Thread(f()).start()
}
}
控制台输出:
INFO-> main 当前用户:oker,工作目录:/Users/oker/IdeaProjects/funtester/,系统编码格式:UTF-8,系统Mac OS X版本:10.16
INFO-> main FunTester
INFO-> Thread-1 tester
Process finished with exit code 0
可以看出Groovy语法是非常简单的,已经非常接近Golang语言go关键字的体验了,仅仅从语法上,性能上的问题以后会再讨论。
线程池升级
因为Golang语言有自己的goroutine管理器,加上Golang特性,所以不是用很担心创建过多的协程消耗更多资源。但是Java还是要考虑一下的,为了解决测试过程中创建过多线程导致异常出现,我用线程池解决这个问题。通过将闭包中的方法包装成java.lang.Runnable
对象,丢给线程池去执行。
封装方法如下:
/**
* 异步执行某个代码块
* Java调用需要return,Groovy也不需要,语法兼容
*
* @param f
*/
public static void fun(Supplier f) {
Runnable runnable = new Runnable() {
@Override
public void run() {
f.get();
}
};
ThreadPoolUtil.executeSync(runnable);
}
这里我选用了Java的语法,为什么不用Groovy的方法封装呢?因为Groovy完全兼容了Java的语法而不失去Groovy自己的特性。
下面演示一下Java如何使用:
public class FunT extends FunLibrary {
public static void main(String[] args) {
fun(()->{
sleep(0.1);
output("FunTester");
return null;
});
output("fds");
ThreadPoolUtil.shutFun()
}
}
优先于Java语法,需要多写一些code,这个我目前解决方案是通过Intellij
自带的Live Templates
输入代码模板解决。如图:
下面演示一下Groovy如何使用:
public static void main(String[] args) {
fun {
sleep(0.2)
output(320)
}
output("FunTester")
ThreadPoolUtil.shutFun()
}
同样我们可以使用Intellij
自带的Live Templates
输入代码模板,不再展示了。
简直如丝般顺滑。
控制台输出:
INFO-> main 当前用户:oker,工作目录:/Users/oker/IdeaProjects/funtester/,系统编码格式:UTF-8,系统Mac OS X版本:10.16
INFO-> main FunTester
INFO-> FT-1 320
Process finished with exit code 0
这里我自定义了线程的名字,方法如下:
/**
* 自定义{@link ThreadFactory}对象
* @return
*/
static ThreadFactory getFactory() {
if (FunFactory == null) {
synchronized (ThreadPoolUtil.class) {
if (FunFactory == null) {
FunFactory = new ThreadFactory() {
@Override
Thread newThread(Runnable runnable) {
Thread thread = new Thread(runnable);
def increment = threadNum.getAndIncrement()
def name = increment < 10 ? "00" + increment : increment < 100 ? "0" + increment : Constant.EMPTY + increment
thread.setName("FT-" + name);
return thread;
}
}
}
}
}
return FunFactory
}
多线程同步
如果遇到有多线程同步需求,那么依旧使用java.util.concurrent.Phaser
来满足需求。封装方法如下:
/**
* 异步执行代码块,使用{@link Phaser}进行多线程同步
*
* @param f
* @param phaser
*/
public static void fun(Supplier f, Phaser phaser) {
Runnable runnable = new Runnable() {
@Override
public void run() {
try {
phaser.register();
f.get();
} catch (Exception e) {
logger.warn("执行异步方法时发生错误!", e);
} finally {
phaser.arriveAndDeregister();
}
}
};
ThreadPoolUtil.executeSync(runnable);
}