Commit fc117cf6 by 仲光辉

init: Initialization project

parents
# Created by .ignore support plugin (hsz.mobi)
### Java template
# Compiled class file
*.class
# Log file
*.log
# BlueJ files
*.ctxt
# Mobile Tools for Java (J2ME)
.mtj.tmp/
# Package Files #
*.war
*.nar
*.ear
*.zip
*.tar.gz
*.rar
# virtual machine crash logs, see http://www.java.com/en/download/help/error_hotspot.xml
hs_err_pid*
.gradle
.idea
*.iml
build
// java,IntelliJ IDEA 插件
plugins {
id 'java'
id 'idea'
}
group 'cn.dankal.share'
description 'dankal-share-java-concurrent-util'
version '1.0-SNAPSHOT'
// 依赖仓库配置
repositories {
// GRADLE_USER_HOME : maven 本地仓库地址
mavenLocal()
maven {
url 'https://maven.aliyun.com/repository/public/'
}
maven {
url 'https://maven.aliyun.com/repository/spring/'
}
maven {
url 'https://maven.aliyun.com/repository/gradle-plugin'
}
maven {
url 'https://maven.aliyun.com/repository/spring-plugin'
}
mavenCentral()
}
// 编译字符集
tasks.withType(JavaCompile) {
options.encoding = "UTF-8"
}
// 使用的 java 版本
java.sourceCompatibility = JavaVersion.VERSION_1_8
java.targetCompatibility = JavaVersion.VERSION_1_8
// 项目依赖
dependencies {
implementation 'cn.hutool:hutool-core:5.5.4'
testImplementation 'org.junit.jupiter:junit-jupiter-api:5.6.0'
testRuntimeOnly 'org.junit.jupiter:junit-jupiter-engine'
}
// 执行测试任务
test {
useJUnitPlatform()
}
\ No newline at end of file
distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
distributionUrl=https\://res.mercymodest.com/gradle-6.7.1-bin.zip
zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists
#!/usr/bin/env sh
#
# Copyright 2015 the original author or authors.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# https://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
#
##############################################################################
##
## Gradle start up script for UN*X
##
##############################################################################
# Attempt to set APP_HOME
# Resolve links: $0 may be a link
PRG="$0"
# Need this for relative symlinks.
while [ -h "$PRG" ] ; do
ls=`ls -ld "$PRG"`
link=`expr "$ls" : '.*-> \(.*\)$'`
if expr "$link" : '/.*' > /dev/null; then
PRG="$link"
else
PRG=`dirname "$PRG"`"/$link"
fi
done
SAVED="`pwd`"
cd "`dirname \"$PRG\"`/" >/dev/null
APP_HOME="`pwd -P`"
cd "$SAVED" >/dev/null
APP_NAME="Gradle"
APP_BASE_NAME=`basename "$0"`
# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"'
# Use the maximum available, or set MAX_FD != -1 to use that value.
MAX_FD="maximum"
warn () {
echo "$*"
}
die () {
echo
echo "$*"
echo
exit 1
}
# OS specific support (must be 'true' or 'false').
cygwin=false
msys=false
darwin=false
nonstop=false
case "`uname`" in
CYGWIN* )
cygwin=true
;;
Darwin* )
darwin=true
;;
MINGW* )
msys=true
;;
NONSTOP* )
nonstop=true
;;
esac
CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
# Determine the Java command to use to start the JVM.
if [ -n "$JAVA_HOME" ] ; then
if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
# IBM's JDK on AIX uses strange locations for the executables
JAVACMD="$JAVA_HOME/jre/sh/java"
else
JAVACMD="$JAVA_HOME/bin/java"
fi
if [ ! -x "$JAVACMD" ] ; then
die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME
Please set the JAVA_HOME variable in your environment to match the
location of your Java installation."
fi
else
JAVACMD="java"
which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
Please set the JAVA_HOME variable in your environment to match the
location of your Java installation."
fi
# Increase the maximum file descriptors if we can.
if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then
MAX_FD_LIMIT=`ulimit -H -n`
if [ $? -eq 0 ] ; then
if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then
MAX_FD="$MAX_FD_LIMIT"
fi
ulimit -n $MAX_FD
if [ $? -ne 0 ] ; then
warn "Could not set maximum file descriptor limit: $MAX_FD"
fi
else
warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT"
fi
fi
# For Darwin, add options to specify how the application appears in the dock
if $darwin; then
GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\""
fi
# For Cygwin or MSYS, switch paths to Windows format before running java
if [ "$cygwin" = "true" -o "$msys" = "true" ] ; then
APP_HOME=`cygpath --path --mixed "$APP_HOME"`
CLASSPATH=`cygpath --path --mixed "$CLASSPATH"`
JAVACMD=`cygpath --unix "$JAVACMD"`
# We build the pattern for arguments to be converted via cygpath
ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null`
SEP=""
for dir in $ROOTDIRSRAW ; do
ROOTDIRS="$ROOTDIRS$SEP$dir"
SEP="|"
done
OURCYGPATTERN="(^($ROOTDIRS))"
# Add a user-defined pattern to the cygpath arguments
if [ "$GRADLE_CYGPATTERN" != "" ] ; then
OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)"
fi
# Now convert the arguments - kludge to limit ourselves to /bin/sh
i=0
for arg in "$@" ; do
CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -`
CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option
if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition
eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"`
else
eval `echo args$i`="\"$arg\""
fi
i=`expr $i + 1`
done
case $i in
0) set -- ;;
1) set -- "$args0" ;;
2) set -- "$args0" "$args1" ;;
3) set -- "$args0" "$args1" "$args2" ;;
4) set -- "$args0" "$args1" "$args2" "$args3" ;;
5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;;
6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;;
7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;;
8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;;
9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;;
esac
fi
# Escape application args
save () {
for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done
echo " "
}
APP_ARGS=`save "$@"`
# Collect all arguments for the java command, following the shell quoting and substitution rules
eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS"
exec "$JAVACMD" "$@"
@rem
@rem Copyright 2015 the original author or authors.
@rem
@rem Licensed under the Apache License, Version 2.0 (the "License");
@rem you may not use this file except in compliance with the License.
@rem You may obtain a copy of the License at
@rem
@rem https://www.apache.org/licenses/LICENSE-2.0
@rem
@rem Unless required by applicable law or agreed to in writing, software
@rem distributed under the License is distributed on an "AS IS" BASIS,
@rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
@rem See the License for the specific language governing permissions and
@rem limitations under the License.
@rem
@if "%DEBUG%" == "" @echo off
@rem ##########################################################################
@rem
@rem Gradle startup script for Windows
@rem
@rem ##########################################################################
@rem Set local scope for the variables with windows NT shell
if "%OS%"=="Windows_NT" setlocal
set DIRNAME=%~dp0
if "%DIRNAME%" == "" set DIRNAME=.
set APP_BASE_NAME=%~n0
set APP_HOME=%DIRNAME%
@rem Resolve any "." and ".." in APP_HOME to make it shorter.
for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi
@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m"
@rem Find java.exe
if defined JAVA_HOME goto findJavaFromJavaHome
set JAVA_EXE=java.exe
%JAVA_EXE% -version >NUL 2>&1
if "%ERRORLEVEL%" == "0" goto execute
echo.
echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
echo.
echo Please set the JAVA_HOME variable in your environment to match the
echo location of your Java installation.
goto fail
:findJavaFromJavaHome
set JAVA_HOME=%JAVA_HOME:"=%
set JAVA_EXE=%JAVA_HOME%/bin/java.exe
if exist "%JAVA_EXE%" goto execute
echo.
echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%
echo.
echo Please set the JAVA_HOME variable in your environment to match the
echo location of your Java installation.
goto fail
:execute
@rem Setup the command line
set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
@rem Execute Gradle
"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %*
:end
@rem End local scope for the variables with windows NT shell
if "%ERRORLEVEL%"=="0" goto mainEnd
:fail
rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
rem the _cmd.exe /c_ return code!
if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1
exit /b 1
:mainEnd
if "%OS%"=="Windows_NT" endlocal
:omega
rootProject.name = 'dankal-share-java-concurrent-util'
package cn.dankal.share.common;
/**
* 插入排序简单实现
*
* @author ZGH.MercyModest
* @version V1.0.0
* @create 2021-01-02
*/
public class InsertionSort {
/**
* 排序方式
*/
public enum Sort {
/**
* 倒序排序
*/
DESC,
/**
* 顺序排序
*/
ASC
}
/**
* 使用插入排序顺序排序目标数组
*
* @param unSortedArray 未排序的数组
* @return int[] 已经排序的数组
*/
public static int[] doAscSort(int[] unSortedArray) {
return doSort(unSortedArray, Sort.ASC);
}
/**
* 使用插入排序倒序排序目标数组
*
* @param unSortedArray 未排序的数组
* @return int[] 已经排序的数组
*/
public static int[] doDescSort(int[] unSortedArray) {
return doSort(unSortedArray, Sort.DESC);
}
/**
* int 数组排序
*
* @param unSortedArray 待排序的数组
* @param sortType 排序类型
* @return 排序好的数组
* @see Sort
*/
public static int[] doSort(int[] unSortedArray, Sort sortType) {
if (unSortedArray.length == 0) {
return unSortedArray;
}
// 默认是 顺序排序
boolean asc = true;
if (Sort.DESC.toString().equalsIgnoreCase(sortType.toString())) {
// 倒序排序
asc = false;
}
// 带排序元素
// 带排序元素前面的元素已经全部排序
int currentValue;
for (int i = 0; i < unSortedArray.length - 1; i++) {
// 已被排序数据的索引
int preIndex = i;
currentValue = unSortedArray[preIndex + 1];
//在已经排序好的元素面中寻找到当前元素合适的位置
while (preIndex >= 0 && (asc ? currentValue < unSortedArray[preIndex] : currentValue > unSortedArray[preIndex])) {
// 从已排序元素开始,将其与带排序元素逐一比较,以寻找带排序元素的合适位置
unSortedArray[preIndex + 1] = unSortedArray[preIndex];
preIndex--;
}
// while 循环结束时,说明已经找到了当前待排序数据的合适位置,插入
unSortedArray[preIndex + 1] = currentValue;
}
return unSortedArray;
}
}
package cn.dankal.share.common;
import cn.hutool.core.util.RandomUtil;
/**
* int 数组工厂
* <p>
* 生成指定长度的 int 数组 并使用随机数填充
* <br/>
* 随机数范围: array.length*3
* </p>
*
* @author ZGH.MercyModest
* @version V1.0.0
* @create 2021-01-02
*/
public class IntArrayFactory {
/**
* 生成的默认数组长度
*/
private final static Integer DEFAULT_ARRAY_LENGTH = 1000000;
/**
* 生成默认长度的 int 数组
*
* @return int []
* @see IntArrayFactory#DEFAULT_ARRAY_LENGTH
*/
public static int[] generateIntArray() {
return generateIntArray(DEFAULT_ARRAY_LENGTH);
}
/**
* 生成指定长度的数组
*
* @param arrayLength 生成长度为 <code>arrayLength</code> int 数组
* @return int []
*/
public static int[] generateIntArray(int arrayLength) {
if (arrayLength <= 0) {
arrayLength = DEFAULT_ARRAY_LENGTH;
}
int[] resultArray = new int[arrayLength];
for (int i = 0; i < resultArray.length; i++) {
resultArray[i] = RandomUtil.randomInt(0, arrayLength * 3);
}
return resultArray;
}
}
package cn.dankal.share.common;
import java.util.concurrent.TimeUnit;
/**
* int 数组 求和工具类
*
* @author ZGH.MercyModest
* @version V1.0.0
* @create 2021-01-02
*/
public class IntArraySumUtil {
/**
* 数组求和
*
* @param targetArray 目标数组
* @param fromIndex from index
* @param toIndex to index
* @return 计算结果
*/
public static int sumArray(int[] targetArray, int fromIndex, int toIndex) {
if (toIndex < fromIndex) {
throw new IllegalArgumentException("fromIndex 必须小于等于 toIndex");
}
if (targetArray.length <= toIndex) {
throw new IllegalArgumentException("toIndex 超出 数组长度范围");
}
int result = 0;
for (int i = fromIndex; i <= toIndex; i++) {
try {
// TODO 休眠 100 毫秒 便于区分不同计算方式和 fork/join 的区别
TimeUnit.MILLISECONDS.sleep(100);
} catch (InterruptedException e) {
// ignore
}
result += targetArray[i];
}
return result;
}
}
package cn.dankal.share.common;
import java.util.Arrays;
/**
* 归并排序简单实现
*
* @author ZGH.MercyModest
* @version V1.0.0
* @create 2021-01-02
*/
public class MergeSort {
/**
* 数组拆分默认临界值
*/
public final static int DEFAULT_ARRAY_SPILT_THRESHOLD = 47;
/**
* 归并排序
*
* @param numArray 需要归并排序的数组
* @return 排序好后的数组
*/
public static int[] sort(int[] numArray) {
if (numArray.length <= DEFAULT_ARRAY_SPILT_THRESHOLD) {
return InsertionSort.doAscSort(numArray);
} else {
/*切分数组,然后递归调用*/
int mid = numArray.length / 2;
int[] left = Arrays.copyOfRange(numArray, 0, mid);
int[] right = Arrays.copyOfRange(numArray, mid, numArray.length);
return mergeSortedArray(sort(left), sort(right));
}
}
/**
* 将两个有序数组合并为一个有序数组(顺序排序)
*
* @param leftArray left array
* @param rightArray right array
* @return 排好序后合并的数组
*/
public static int[] mergeSortedArray(int[] leftArray, int[] rightArray) {
int[] result = new int[leftArray.length + rightArray.length];
for (int index = 0, i = 0, j = 0; index < result.length; index++) {
if (i >= leftArray.length) {
// 左边数组已经取完,完全取右边数组的值即可
result[index] = rightArray[j++];
} else if (j >= rightArray.length) {
// 右边数组已经取完,完全取左边数组的值即可
result[index] = leftArray[i++];
} else if (leftArray[i] > rightArray[j]) {
// 左边数组的元素值大于右边数组,取右边数组的值
result[index] = rightArray[j++];
} else {
// 右边数组的元素值大于左边数组,取左边数组的值
result[index] = leftArray[i++];
}
}
return result;
}
}
package cn.dankal.share.common;
import cn.hutool.core.util.StrUtil;
import java.util.concurrent.ArrayBlockingQueue;
import java.util.concurrent.ThreadFactory;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
/**
* ThreadExecutor 工厂
*
* @author ZGH.MercyModest
* @version V1.0.0
* @create 2021-01-02
*/
public class ThreadExecutorFactory {
/**
* 获取 ThreadPoolExecutor
*
* @param corePoolSize 核心线程数
* @param maximumPoolSize 最大线程数
* @param keepAliveTime 超过核心线程数的线程存活时间
* @param timeUnit 超过核心线程数的线程存活时间单位
* @param threadNamePrefix 创建线程的名称前缀
* @param blockQueueCapacity 线程池阻塞队列的容量
* @return ThreadPoolExecutor
* @see ThreadPoolExecutor
*/
public static ThreadPoolExecutor getThreadPoolExecutor(int corePoolSize,
int maximumPoolSize,
long keepAliveTime,
TimeUnit timeUnit,
String threadNamePrefix,
int blockQueueCapacity) {
// 参数校验
if (corePoolSize <= 0) {
throw new IllegalArgumentException("corePoolSize 必须大于0");
}
if (maximumPoolSize <= 0) {
throw new IllegalArgumentException("maximumPoolSize 必须大于0");
}
if (keepAliveTime < 0) {
throw new IllegalArgumentException("keepAliveTime 必须大于0");
}
if (null == timeUnit) {
throw new IllegalArgumentException("timeUnit 不能为空");
}
if (corePoolSize > maximumPoolSize) {
throw new IllegalArgumentException("corePoolSize 必须小于等于 maximumPoolSize");
}
if (StrUtil.isBlank(threadNamePrefix)) {
throw new IllegalArgumentException("threadNamePrefix 不能为空");
}
if (blockQueueCapacity <= 0) {
throw new IllegalArgumentException("blockQueueCapacity 必须大于0");
}
return new ThreadPoolExecutor(
corePoolSize,
maximumPoolSize,
keepAliveTime,
timeUnit,
new ArrayBlockingQueue<>(blockQueueCapacity),
threadFactory(threadNamePrefix)
);
}
/**
* ThreadFactory 简答实现 --> 为线程池线程指定名称
*
* @param threadNamePrefix 线程池线程名称前缀
* @return ThreadFactory
*/
private static ThreadFactory threadFactory(final String threadNamePrefix) {
return (runnable -> {
Thread thread = new Thread(runnable);
long id = thread.getId();
thread.setName(threadNamePrefix + " - " + id);
return thread;
});
}
/**
* 获取一个 <code>ThreadPoolExecutor</code>
* <p>
* coreSize: 6
* <br/>
* maximumPoolSize: 10
* <br/>
* KeepAliveTime: 10
* <br/>
* unit: TimeUnit.MILLISECONDS
* <br/>
* blockQueueCapacity: 100
* <br/>
* </p>
*
* @param threadNamePrefix <code>threadNamePrefix</code>
* @return ThreadPoolExecutor
*/
public static ThreadPoolExecutor getThreadPoolExecutor(final String threadNamePrefix) {
return ThreadExecutorFactory.getThreadPoolExecutor(6,
10,
10,
TimeUnit.MILLISECONDS,
threadNamePrefix,
100);
}
}
package cn.dankal.share.concurrentutil;
import cn.dankal.share.common.ThreadExecutorFactory;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.ThreadPoolExecutor;
/**
* CountDownLatch 简单使用测试
*
* @author ZGH.MercyModest
* @version V1.0.0
* @create 2021-01-02
*/
public class CountDownLatchTest {
public static void main(String[] args) {
ThreadPoolExecutor threadPoolExecutor = ThreadExecutorFactory.getThreadPoolExecutor("countDownLatch-test");
try {
/*
情景小析:
我们需要做这样一件事情:
target: 获取商品详情
- 获取商品信息 耗时 50 毫秒
- 获取商品价格 耗时 10 毫秒
- 商品属性获取并归并(Sku/Spu) 耗时 65 毫秒
- 假设三个子任务之间没有依赖关系
*/
// target: 获取商品详细
CountDownLatch countDownLatch = new CountDownLatch(3);
final long startTime = System.currentTimeMillis();
// 获取商品信息 50 毫秒
final long getProductDetailElapsed = 50;
Worker getProductDetailWorker = new Worker("获取商品信息", getProductDetailElapsed, countDownLatch);
threadPoolExecutor.execute(getProductDetailWorker);
// 获取商品价格 10 毫秒
final long getPriceElapsed = 10;
Worker getProductPrice = new Worker("获取商品价格", getPriceElapsed, countDownLatch);
threadPoolExecutor.execute(getProductPrice);
//商品属性获取并归并 65毫秒
final long productPropertiesReduceElapsed = 65;
Worker productPropertiesReduceWorker = new Worker("商品属性获取并归并", productPropertiesReduceElapsed, countDownLatch);
threadPoolExecutor.execute(productPropertiesReduceWorker);
try {
System.out.printf("%s 线程即将开始等待 ... ...%n", Thread.currentThread().getName());
countDownLatch.await();
} catch (InterruptedException e) {
// ignored
}
System.out.println("接口返回~");
System.out.println("接口放回理论耗时: " + (getProductDetailElapsed + getPriceElapsed + productPropertiesReduceElapsed) + " 毫秒");
System.out.println("接口放回实际耗时: " + (System.currentTimeMillis() - startTime) + " 毫秒");
} finally {
threadPoolExecutor.shutdown();
}
}
}
package cn.dankal.share.concurrentutil;
import cn.dankal.share.common.ThreadExecutorFactory;
import java.util.concurrent.CyclicBarrier;
import java.util.concurrent.ThreadPoolExecutor;
/**
* CyclicBarrier 简单使用测试
*
* @author ZGH.MercyModest
* @version V1.0.0
* @create 2021-01-02
*/
public class CyclicBarrierTest {
public static void main(String[] args) {
ThreadPoolExecutor threadPoolExecutor = ThreadExecutorFactory.getThreadPoolExecutor("cyclicBarrier-test");
try {
/*
情景小析:
我们需要做这样一件事情:
target: 获取商品详情
- 获取商品信息 耗时 50 毫秒
- 获取商品价格 耗时 10 毫秒
- 商品属性获取并归并(Sku/Spu) 耗时 65 毫秒
- 假设三个子任务之间没有依赖关系
*/
// target: 获取商品详细
CyclicBarrier cyclicBarrier = new CyclicBarrier(4);
final long startTime = System.currentTimeMillis();
// 获取商品信息 50 毫秒
final long getProductDetailElapsed = 50;
Worker getProductDetailWorker = new Worker("获取商品信息", getProductDetailElapsed, cyclicBarrier);
threadPoolExecutor.execute(getProductDetailWorker);
// 获取商品价格 10 毫秒
final long getPriceElapsed = 10;
Worker getProductPrice = new Worker("获取商品价格", getPriceElapsed, cyclicBarrier);
threadPoolExecutor.execute(getProductPrice);
//商品属性获取并归并 65毫秒
final long productPropertiesReduceElapsed = 65;
Worker productPropertiesReduceWorker = new Worker("商品属性获取并归并", productPropertiesReduceElapsed, cyclicBarrier);
threadPoolExecutor.execute(productPropertiesReduceWorker);
try {
System.out.printf("%s 线程即将开始等待 ... ...%n", Thread.currentThread().getName());
cyclicBarrier.await();
} catch (Exception e) {
// ignored
}
System.out.println("接口返回~");
System.out.println("接口放回理论耗时: " + (getProductDetailElapsed + getPriceElapsed + productPropertiesReduceElapsed) + " 毫秒");
System.out.println("接口放回实际耗时: " + (System.currentTimeMillis() - startTime) + " 毫秒");
} finally {
threadPoolExecutor.shutdown();
}
}
}
package cn.dankal.share.concurrentutil;
import cn.dankal.share.common.ThreadExecutorFactory;
import java.util.HashMap;
import java.util.UUID;
import java.util.concurrent.Exchanger;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
/**
* {@link Exchanger} 的简单使用
*
* @author ZGH.MercyModest
* @version V1.0.0
* @create 2021-01-02
*/
public class ExchangerTest {
public static void main(String[] args) {
/*
应用场景:
- 在两个线程之间进行数据交换
- 流水线式数据处理
- 线程一 处理完数据之后,将数据交给 线程二继续处理
*/
ThreadPoolExecutor threadPoolExecutor = ThreadExecutorFactory.getThreadPoolExecutor("exchanger-test");
Exchanger<HashMap<String, Object>> mapExchanger = new Exchanger<>();
try {
// 线程一 先处理
threadPoolExecutor.execute(() -> {
final HashMap<String, Object> dataMap = new HashMap<>(1 << 5);
dataMap.put(Thread.currentThread().getName(), UUID.randomUUID().toString().replace("-", ""));
System.out.println(Thread.currentThread().getName() + " 开始处理数据");
try {
TimeUnit.SECONDS.sleep(1);
System.out.printf("%s 线程 原始数据: %s%n", Thread.currentThread().getName(), dataMap);
System.out.println(Thread.currentThread().getName() + " 完成数据处理,即将进行数据传递");
HashMap<String, Object> exchangeResult = mapExchanger.exchange(dataMap);
System.out.printf("%s 获取交换后的结果 %s%n", Thread.currentThread().getName(), exchangeResult);
} catch (InterruptedException e) {
// ignored
}
});
// 线程二 继续处理
threadPoolExecutor.execute(() -> {
System.out.println(Thread.currentThread().getName() + " 开始处理数据");
try {
HashMap<String, Object> emptyMap = new HashMap<>(1);
TimeUnit.SECONDS.sleep(1);
System.out.printf("%s 线程 原始数据: %s%n", Thread.currentThread().getName(), emptyMap);
System.out.println(Thread.currentThread().getName() + " 完成数据处理,即将进行数据传递");
HashMap<String, Object> exchangeResult = mapExchanger.exchange(emptyMap);
System.out.printf("%s 获取交换后的结果 %s%n", Thread.currentThread().getName(), exchangeResult);
} catch (InterruptedException e) {
// ignored
}
});
} finally {
threadPoolExecutor.shutdown();
}
}
}
package cn.dankal.share.concurrentutil;
import cn.dankal.share.common.ThreadExecutorFactory;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.Semaphore;
import java.util.concurrent.ThreadPoolExecutor;
/**
* Semaphore 简单使用测试
*
* @author ZGH.MercyModest
* @version V1.0.0
* @create 2021-01-02
*/
public class SemaphoreTest {
public static void main(String[] args) {
ThreadPoolExecutor threadPoolExecutor = ThreadExecutorFactory.getThreadPoolExecutor("semaphore-test");
// semaphore: 令牌桶只有 3 个令牌
Semaphore semaphore = new Semaphore(3);
// 参与抢令牌的线程数
final int count = 10;
// 确保统计顺序执行完(成功/失败统计)
CountDownLatch successAndFairCountDownLatch = new CountDownLatch(count);
TokenBucket tokenBucket = new TokenBucket(semaphore, successAndFairCountDownLatch);
try {
for (int i = 0; i < count; i++) {
threadPoolExecutor.execute(tokenBucket::getToken);
}
successAndFairCountDownLatch.await();
System.out.println("参与线程数: " + count);
System.out.println("成功线程数:" + tokenBucket.successCount());
System.out.println("失败线程数:" + tokenBucket.fairCount());
} catch (InterruptedException e) {
// ignored
} finally {
threadPoolExecutor.shutdown();
}
}
}
package cn.dankal.share.concurrentutil;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.Semaphore;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicInteger;
/**
* token Bucket 简单模拟
* <p>
* 基于 {@link Semaphore} 简单实现
* </p>
*
* @author ZGH.MercyModest
* @version V1.0.0
* @create 2021-01-02
*/
public class TokenBucket {
/**
* <code>Semaphore</code>
*/
private final Semaphore semaphore;
/**
* <code>CountDownLatch</code>
*/
private final CountDownLatch countDownLatch;
/**
* 令牌统计
*/
private final AtomicInteger tokenCounter = new AtomicInteger(0);
/**
* 成功线程数统计
*/
private final AtomicInteger successCounter = new AtomicInteger(0);
/**
* 失败线程数统计
*/
private final AtomicInteger fairCounter = new AtomicInteger(0);
public TokenBucket(Semaphore semaphore, CountDownLatch countDownLatch1) {
this.semaphore = semaphore;
this.countDownLatch = countDownLatch1;
}
/**
* 获取令牌
*/
public void getToken() {
try {
final long getTokenTimeout = 5;
if (semaphore.tryAcquire(getTokenTimeout, TimeUnit.MILLISECONDS)) {
// 成功获取到信号量: 获取令牌成功
// 保证 tryAcquire 超时
int tokenNum = tokenCounter.incrementAndGet();
TimeUnit.SECONDS.sleep(1);
System.out.printf("%s 成功获取到 %d 号令牌 %n", Thread.currentThread().getName(), tokenNum);
successCounter.incrementAndGet();
} else {
// 获取令牌失败
System.out.printf("%s 未获取到令牌 %n", Thread.currentThread().getName());
fairCounter.incrementAndGet();
}
} catch (InterruptedException e) {
// ignored
} finally {
// 成功/失败统计计数扣减
this.countDownLatch.countDown();
}
}
/**
* 成功获取令桶统计
*
* @return 成功获取令牌统计
*/
public int successCount() {
return successCounter.get();
}
/**
* 获取令牌失败统计
*
* @return 未成功获取令牌统计
*/
public int fairCount() {
return fairCounter.get();
}
}
package cn.dankal.share.concurrentutil;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.CyclicBarrier;
import java.util.concurrent.TimeUnit;
/**
* {@link Runnable}
* Worker 简单模拟
* <p>
* 用于测试 {@link CountDownLatch} 和 {@link CyclicBarrier} 的简单使用
* </p>
*
* @author ZGH.MercyModest
* @version V1.0.0
* @create 2021-01-02
*/
public class Worker implements Runnable {
/**
* 工作内容
*/
private final String workContent;
/**
* 工作消耗时间:单位 second
*/
private final long workTimeInMilliSecond;
/**
* <code>CountDownLatch</code>
*/
private CountDownLatch countDownLatch;
/**
* <code>CyclicBarrier</code>
*/
private CyclicBarrier cyclicBarrier;
public Worker(String workContent, long workTimeInMilliSecond, CountDownLatch countDownLatch) {
this.workContent = workContent;
this.workTimeInMilliSecond = workTimeInMilliSecond;
this.countDownLatch = countDownLatch;
}
public Worker(String workContent, long workTimeInMilliSecond, CyclicBarrier cyclicBarrier) {
this.workContent = workContent;
this.workTimeInMilliSecond = workTimeInMilliSecond;
this.cyclicBarrier = cyclicBarrier;
}
@Override
public void run() {
// 工作耗时
try {
TimeUnit.MILLISECONDS.sleep(workTimeInMilliSecond);
System.out.printf("%s 正在: %s 预计耗时 %d 毫秒 %n", Thread.currentThread().getName(), workContent, workTimeInMilliSecond);
} catch (InterruptedException e) {
// ignored
} finally {
if (null != this.countDownLatch) {
// 计数扣减
this.countDownLatch.countDown();
} else if (null != cyclicBarrier) {
try {
// 等待全部执行完成
this.cyclicBarrier.await();
} catch (Exception e) {
// ignored
}
}
}
}
}
package cn.dankal.share.forkjoin;
import cn.hutool.core.collection.CollectionUtil;
import cn.hutool.core.io.FileUtil;
import cn.hutool.core.util.ArrayUtil;
import java.io.File;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.RecursiveAction;
import java.util.concurrent.atomic.AtomicInteger;
/**
* 基于 {@link RecursiveAction} (ForkJoin)实现 文件查找与简单统计
*
* @author ZGH.MercyModest
* @version V1.0.0
* @create 2021-01-02
*/
public class FindFileTask extends RecursiveAction {
/**
* 目标文件夹
*/
private final String targetFolder;
/**
* 目标文件后缀
*/
private final String findFileSuffix;
/**
* 目标文件计数器
*/
public final static AtomicInteger COUNTER_TARGET_FILE = new AtomicInteger(0);
/**
* 遍历目录计数器
*/
public final static AtomicInteger COUNTER_EACH_FOLDER = new AtomicInteger(0);
public FindFileTask(String targetFolder, String findFileSuffix) {
this.targetFolder = targetFolder;
this.findFileSuffix = findFileSuffix;
}
@Override
protected void compute() {
// 参数校验
if (!FileUtil.exist(targetFolder) || FileUtil.isFile(targetFolder)) {
throw new IllegalArgumentException("targetFolder 必须是存在的 文件夹");
}
File targetFile = FileUtil.file(targetFolder);
File[] files = targetFile.listFiles();
if (ArrayUtil.isEmpty(files)) {
// 空文件夹直接返回
return;
}
// 需要执行 Task List
List<FindFileTask> subFindFileTask = new ArrayList<>(1 << 5);
for (File file : files) {
if (file.isDirectory()) {
// 文件夹
FindFileTask findFileTask = new FindFileTask(file.getAbsolutePath(), findFileSuffix);
subFindFileTask.add(findFileTask);
COUNTER_EACH_FOLDER.incrementAndGet();
} else {
// 文件
String fileSuffix = FileUtil.getSuffix(file);
if (("." + fileSuffix).equalsIgnoreCase(findFileSuffix)) {
// 是目标文件
System.out.println("find: " + file.getName());
COUNTER_TARGET_FILE.incrementAndGet();
}
}
}
if (CollectionUtil.isNotEmpty(subFindFileTask)) {
// 执行所有拆分的 task 如果需要的话
for (FindFileTask findFileTask : invokeAll(subFindFileTask)) {
// 因为是拆分 即使我们不需要获取拆分结果也需要 join 切切
findFileTask.join();
}
}
}
}
package cn.dankal.share.forkjoin;
import cn.dankal.share.common.InsertionSort;
import cn.dankal.share.common.MergeSort;
import java.util.Arrays;
import java.util.concurrent.RecursiveTask;
/**
* 基于 {@link RecursiveTask} (ForkJoin) 简单实现 数组排序(归并排序)
*
* @author ZGH.MercyModest
* @version V1.0.0
* @create 2021-01-02
*/
public class IntArraySortTask extends RecursiveTask<int[]> {
/**
* 需要排序的数组
*/
private final int[] intArray;
/**
* 数组拆分临界值(length)
*/
private final int thresholdLength;
public IntArraySortTask(int[] intArray, int thresholdLength) {
this.intArray = intArray;
this.thresholdLength = thresholdLength;
}
@Override
protected int[] compute() {
if (intArray.length <= thresholdLength) {
// 达到拆分临界 直接排序
return InsertionSort.doAscSort(intArray);
} else {
// formIndex ... middle ... toIndex
// 任务拆分 将排序任务拆分成两个或更多
// 此处演示 拆分为 左右两个任务
int middle = intArray.length / 2;
// 将目标数组从 “中间”拆分
int[] leftIntArray = Arrays.copyOfRange(intArray, 0, middle);
int[] rightIntArray = Arrays.copyOfRange(intArray, middle, intArray.length);
// 拆分后,分为 左,右两个子Task
IntArraySortTask leftSortTask = new IntArraySortTask(leftIntArray, thresholdLength);
IntArraySortTask rightSortTask = new IntArraySortTask(rightIntArray, thresholdLength);
//执行所有 Task
invokeAll(leftSortTask, rightSortTask);
// 归并 left 的结果
int[] leftArray = leftSortTask.join();
// 归并 right 的结果
int[] rightArray = rightSortTask.join();
// 合并 归并结果
return MergeSort.mergeSortedArray(leftArray, rightArray);
}
}
}
package cn.dankal.share.forkjoin;
import cn.dankal.share.common.IntArraySumUtil;
import java.util.concurrent.RecursiveTask;
/**
* 基于 {@link RecursiveTask} 简单实现数组求和
* <p>
* 简单测试对比,展示 ForkJoin 的优势
* </p>
*
* @author ZGH.MercyModest
* @version V1.0.0
* @create 2021-01-02
*/
public class IntArraySumTask extends RecursiveTask<Integer> {
/**
* 任务拆分临界值
*/
private final int thresholdLength;
/**
* 求和目标数组
*/
private final int[] intArray;
/**
* fromIndex
*/
private final int fromIndex;
/**
* toIndex
*/
private final int toIndex;
public IntArraySumTask(int thresholdLength, int[] intArray, int fromIndex, int toIndex) {
this.thresholdLength = thresholdLength;
this.intArray = intArray;
this.fromIndex = fromIndex;
this.toIndex = toIndex;
}
@Override
protected Integer compute() {
if (toIndex - fromIndex <= thresholdLength) {
return IntArraySumUtil.sumArray(intArray, fromIndex, toIndex);
} else {
//拆分成两个子 task 任务
int middleIndex = (fromIndex + toIndex) / 2;
IntArraySumTask leftSumTask = new IntArraySumTask(thresholdLength, intArray, fromIndex, middleIndex);
IntArraySumTask rightSumTask = new IntArraySumTask(thresholdLength, intArray, middleIndex + 1, toIndex);
invokeAll(leftSumTask, rightSumTask);
// 归并拆分执行结果并返回
return leftSumTask.join() + rightSumTask.join();
}
}
}
package cn.dankal.share.util;
import cn.dankal.share.common.IntArrayFactory;
import cn.dankal.share.common.IntArraySumUtil;
import cn.dankal.share.common.MergeSort;
import cn.dankal.share.forkjoin.FindFileTask;
import cn.dankal.share.forkjoin.IntArraySortTask;
import cn.dankal.share.forkjoin.IntArraySumTask;
import cn.hutool.core.util.RandomUtil;
import jdk.nashorn.internal.ir.annotations.Ignore;
import org.junit.jupiter.api.Test;
import java.util.Arrays;
import java.util.LinkedHashMap;
import java.util.Map;
import java.util.concurrent.ForkJoinPool;
import java.util.concurrent.TimeUnit;
/**
* Fork/Join 相关测试类
*
* @author ZGH.MercyModest
* @version V1.0.0
* @create 2021-01-02
*/
public class ForkJoinTest {
/**
* 测试 Fork/Join 实现的 归并排序
*/
@Test
public void testMergeSortByForkJoin() {
int[] generateIntArray = IntArrayFactory.generateIntArray(25);
System.out.println("排序前:");
System.out.println(Arrays.toString(generateIntArray));
System.out.println("========================");
ForkJoinPool forkJoinPool = new ForkJoinPool();
IntArraySortTask intArraySortTask = new IntArraySortTask(generateIntArray, 2);
int[] invokeResult = forkJoinPool.invoke(intArraySortTask);
System.out.println("排序后:");
System.out.println(Arrays.toString(invokeResult));
System.out.println("========================");
}
/**
* 测试 Fork/Join 实现 数组求和
*/
@Test
@Ignore
public void testArraySortByForkJoin() {
int[] generateIntArray = IntArrayFactory.generateIntArray(50);
IntArraySumTask intArraySumTask = new IntArraySumTask(10, generateIntArray, 0, generateIntArray.length - 1);
ForkJoinPool forkJoinPool = new ForkJoinPool();
Integer invokeResult = forkJoinPool.invoke(intArraySumTask);
System.out.println(invokeResult);
}
/**
* int 求和
*
* @param left 被加数
* @param right 加数
* @return 和
*/
private static int intSum(int left, int right) {
try {
TimeUnit.MILLISECONDS.sleep(100);
} catch (InterruptedException e) {
// ignore
}
return left + right;
}
/**
* 测试 归并排序对比
*/
@Test
public void testMergeSortThreeWays() {
int[] generateIntArray = IntArrayFactory.generateIntArray(3000000);
System.out.println("数组元素: " + generateIntArray.length);
// general merge sort
final long generalMergeSortStart = System.currentTimeMillis();
MergeSort.sort(generateIntArray);
System.out.println("运行耗时: " + (System.currentTimeMillis() - generalMergeSortStart) + " 毫秒 (general mergeSort)");
// jdk utils: Arrays#sort
final long arraySortStartMs = System.currentTimeMillis();
// DualPivotQuicksort 快排
Arrays.sort(generateIntArray);
System.out.println("运行耗时: " + (System.currentTimeMillis() - arraySortStartMs) + " 毫秒 (Arrays#sort)");
// Fork/Join merge sort
ForkJoinPool forkJoinPool = new ForkJoinPool();
IntArraySortTask intArraySortTask = new IntArraySortTask(generateIntArray, 300);
final long forkJoinStartMs = System.currentTimeMillis();
forkJoinPool.invoke(intArraySortTask);
System.out.println("运行耗时: " + (System.currentTimeMillis() - forkJoinStartMs) + " 毫秒 (Fork/Join mergeSort)");
}
/**
* 测试: Fork/Join的优势
*/
@Test
public void testAdvantageOfForkJoin() {
Map<String, Long> expenditureTimeMap = new LinkedHashMap<>(1 << 4);
final String streamDataKey = "java8 stream";
final String forkJoinDataKey = "fork join";
final String generalSumDataKey = "general sum";
final int count = 10;
for (int i = 0; i < count; i++) {
int[] generateIntArray = IntArrayFactory.generateIntArray(RandomUtil.randomInt(50, 100));
System.out.printf("目标数组 length: %d%n ", generateIntArray.length);
System.out.println(Arrays.toString(generateIntArray));
System.out.printf("============= 第 %d 次计算 =============%n", (i + 1));
final long streamStartMs = System.currentTimeMillis();
int result = Arrays.stream(generateIntArray)
.parallel()
.reduce(ForkJoinTest::intSum)
.orElse(-1);
long streamExpenditure = System.currentTimeMillis() - streamStartMs;
System.out.println("(java8 stream) 运行结果: " + result);
System.out.println("(java8 stream) 运行耗时: " + streamExpenditure + " 毫秒\n");
// 将 java8 stream 每次执行时间存储在 LinkedHashMap 中
if (!expenditureTimeMap.containsKey(streamDataKey)) {
expenditureTimeMap.put(streamDataKey, streamExpenditure);
} else {
Long oldExpenditure = expenditureTimeMap.get(streamDataKey);
expenditureTimeMap.put(streamDataKey, oldExpenditure + streamExpenditure);
}
IntArraySumTask intArraySumTask = new IntArraySumTask(10, generateIntArray, 0, generateIntArray.length - 1);
ForkJoinPool forkJoinPool = new ForkJoinPool();
final long forkJoinStartMs = System.currentTimeMillis();
Integer invokeResult = forkJoinPool.invoke(intArraySumTask);
long forkJoinExpenditure = System.currentTimeMillis() - forkJoinStartMs;
System.out.println("(fork/join) 运行结果: " + invokeResult);
System.out.println("(fork/join) 运行耗时: " + forkJoinExpenditure + " 毫秒\n");
// 将 fork/join 每次执行时间存储在 LinkedHashMap 中
if (!expenditureTimeMap.containsKey(forkJoinDataKey)) {
expenditureTimeMap.put(forkJoinDataKey, forkJoinExpenditure);
} else {
Long oldExpenditure = expenditureTimeMap.get(forkJoinDataKey);
expenditureTimeMap.put(forkJoinDataKey, oldExpenditure + forkJoinExpenditure);
}
final long generalStartMs = System.currentTimeMillis();
int sum = IntArraySumUtil.sumArray(generateIntArray, 0, generateIntArray.length - 1);
long generalSumExpenditure = System.currentTimeMillis() - generalStartMs;
System.out.println("(general sum) 运行结果: " + sum);
System.out.println("(general sum) 运行耗时: " + generalSumExpenditure + " 毫秒");
// 将 general sum 每次执行时间存储在 LinkedHashMap 中
if (!expenditureTimeMap.containsKey(generalSumDataKey)) {
expenditureTimeMap.put(generalSumDataKey, generalSumExpenditure);
} else {
Long oldExpenditure = expenditureTimeMap.get(generalSumDataKey);
expenditureTimeMap.put(generalSumDataKey, oldExpenditure + generalSumExpenditure);
}
System.out.println("=============================\n");
}
System.out.println("平均耗时");
for (String key : expenditureTimeMap.keySet()) {
System.out.printf("%s 运行 %d 次,平均耗时 %d 毫秒%n", key, count, expenditureTimeMap.get(key) / count);
}
}
/**
* 测试: Fork/Join RecursiveAction: 目标文件查找
*/
@Test
public void testFindFileByRecursiveAction() {
final String targetFolder = "C:/dev/MavenRepository";
final String findFileSuffix = ".lastUpdated";
FindFileTask findFileTask = new FindFileTask(targetFolder, findFileSuffix);
ForkJoinPool forkJoinPool = new ForkJoinPool();
forkJoinPool.invoke(findFileTask);
System.out.printf("共遍历 : %d 个文件夹%n", FindFileTask.COUNTER_EACH_FOLDER.get());
System.out.printf("找到目标文件 %s : %d 个%n", findFileSuffix, FindFileTask.COUNTER_TARGET_FILE.get());
}
}
package cn.dankal.share.util;
import cn.dankal.share.common.InsertionSort;
import cn.dankal.share.common.IntArrayFactory;
import cn.dankal.share.common.MergeSort;
import org.junit.jupiter.api.Test;
import java.util.Arrays;
/**
* Util 工具类测试类
*
* @author ZGH.MercyModest
* @version V1.0.0
* @create 2021-01-02
*/
public class UtilMainTest {
/**
* 测试 插入排序
*/
@Test
public void testInsertSort() {
int[] generateIntArray = IntArrayFactory.generateIntArray(20);
System.out.println("排序前:");
System.out.println(Arrays.toString(generateIntArray));
System.out.println("========================");
InsertionSort.doAscSort(generateIntArray);
System.out.println("顺序排序后:");
System.out.println(Arrays.toString(generateIntArray));
System.out.println("倒序排序后:");
InsertionSort.doDescSort(generateIntArray);
System.out.println(Arrays.toString(generateIntArray));
System.out.println("========================");
}
/**
* 测试 MergeSort
*/
@Test
public void testMergeSort() {
int[] generateIntArray = IntArrayFactory.generateIntArray(25);
System.out.println("排序前:");
System.out.println(Arrays.toString(generateIntArray));
System.out.println("========================");
MergeSort.sort(generateIntArray);
System.out.println("排序后:");
System.out.println(Arrays.toString(generateIntArray));
System.out.println("========================");
}
}
package cn.dankal.share.util.completablefuture;
import cn.dankal.share.common.ThreadExecutorFactory;
import cn.hutool.core.util.RandomUtil;
import org.junit.jupiter.api.AfterAll;
import org.junit.jupiter.api.Test;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
/**
* @author ZGH.MercyModest
* @version V1.0.0
* @create 2021-01-02
*/
public class CompletableFutureAuxiliaryMethodTest {
/**
* ThreadPoolExecutor
*/
private final static ThreadPoolExecutor THREAD_POOL_EXECUTOR = ThreadExecutorFactory.getThreadPoolExecutor("completableFuture-result-get-test");
/**
* 测试 CompletableFuture:allOf(CompletableFuture<?>... cfs)
*/
@Test
public void testAllOf() {
Runnable runnable = () -> {
try {
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
// ignored
}
System.out.printf("线程 %s 完成工作%n", Thread.currentThread().getName());
};
CompletableFuture<Void> completableFuture01 = CompletableFuture.runAsync(runnable);
CompletableFuture<Void> completableFuture02 = CompletableFuture.runAsync(runnable);
CompletableFuture<Void> completableFuture03 = CompletableFuture.runAsync(runnable);
CompletableFuture<Void> completableFuture04 = CompletableFuture.runAsync(runnable);
CompletableFuture<Void> completableFuture05 = CompletableFuture.runAsync(runnable);
System.out.println("all completableFuture is starting the work or will start work");
CompletableFuture.allOf(completableFuture01, completableFuture02, completableFuture03, completableFuture04, completableFuture05)
.join();
System.out.println("completableFuture all finished the work");
}
/**
* 测试 CompletableFuture:anyOf(CompletableFuture<?>... cfs)
*/
@Test
public void testAnyOf() {
Runnable runnable = () -> {
try {
TimeUnit.SECONDS.sleep(RandomUtil.randomInt(1,10));
} catch (InterruptedException e) {
// ignored
}
System.out.printf("线程 %s 完成工作%n", Thread.currentThread().getName());
};
CompletableFuture<Void> completableFuture01 = CompletableFuture.runAsync(runnable);
CompletableFuture<Void> completableFuture02 = CompletableFuture.runAsync(runnable);
CompletableFuture<Void> completableFuture03 = CompletableFuture.runAsync(runnable);
CompletableFuture<Void> completableFuture04 = CompletableFuture.runAsync(runnable);
CompletableFuture<Void> completableFuture05 = CompletableFuture.runAsync(runnable);
System.out.println("all completableFuture is starting the work or will start work");
CompletableFuture.anyOf(completableFuture01, completableFuture02, completableFuture03, completableFuture04, completableFuture05)
.join();
System.out.println("completableFuture all finished the work");
}
/**
* 关闭 线程池资源
*/
@AfterAll
public static void after() {
THREAD_POOL_EXECUTOR.shutdown();
}
}
package cn.dankal.share.util.completablefuture;
import cn.dankal.share.common.ThreadExecutorFactory;
import cn.hutool.core.util.RandomUtil;
import cn.hutool.core.util.StrUtil;
import org.junit.jupiter.api.AfterAll;
import org.junit.jupiter.api.Test;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
/**
* @author ZGH.MercyModest
* @version V1.0.0
* @create 2021-01-02
*/
public class CompletableFutureMethodTest {
/**
* ThreadPoolExecutor
*/
private final static ThreadPoolExecutor THREAD_POOL_EXECUTOR = ThreadExecutorFactory.getThreadPoolExecutor("completableFuture-test");
/**
* 测试 CompletableFuture: thenApply(Function<? super T,? extends U> fn)
*/
@Test
public void testThenApply() throws ExecutionException, InterruptedException {
final long startMs = System.currentTimeMillis();
CompletableFuture<Integer> completableFuture = CompletableFuture
.supplyAsync(() -> {
try {
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
// ignored
}
int randomInt = RandomUtil.randomInt(1, 100);
System.out.printf("%s 成功生产了 %d%n", Thread.currentThread().getName(), randomInt);
return randomInt;
}, THREAD_POOL_EXECUTOR)
.thenApply(result -> {
System.out.printf("%s 成功获取结果,耗时 %d 秒%n", Thread.currentThread().getName(), (System.currentTimeMillis() - startMs) / 1000);
System.out.printf("%s 线程即将消费 %d%n", Thread.currentThread().getName(), result);
return result + 100;
});
Integer result = completableFuture.get();
System.out.println("result = " + result);
}
/**
* 测试 CompletableFuture: thenApplyAsync(Function<? super T,? extends U> fn, Executor executor)
*/
@Test
public void testThenApplyAsync() throws ExecutionException, InterruptedException {
final long startMs = System.currentTimeMillis();
CompletableFuture<Integer> completableFuture = CompletableFuture
.supplyAsync(() -> {
try {
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
// ignored
}
int randomInt = RandomUtil.randomInt(1, 100);
System.out.printf("%s 成功生产了 %d%n", Thread.currentThread().getName(), randomInt);
return randomInt;
}, THREAD_POOL_EXECUTOR)
.thenApplyAsync(result -> {
System.out.printf("%s 成功获取结果,耗时 %d 秒%n", Thread.currentThread().getName(), (System.currentTimeMillis() - startMs) / 1000);
System.out.printf("%s 线程即将消费 %d%n", Thread.currentThread().getName(), result);
return result + 100;
}, THREAD_POOL_EXECUTOR);
Integer result = completableFuture.get();
System.out.println("result = " + result);
}
/**
* 测试 CompletableFuture:
* <pre>
* {@code
* thenCombineAsync(
* CompletionStage<? extends U> other,
* BiFunction<? super T,? super U,? extends V> fn, Executor executor)
* }
* </pre>
*/
@Test
public void testThenCombineAsync() throws ExecutionException, InterruptedException {
CompletableFuture<Integer> integerCompletableFuture = CompletableFuture.supplyAsync(() -> {
int randomInt = RandomUtil.randomInt(1, 100);
final long sleepTime = 5;
try {
TimeUnit.SECONDS.sleep(sleepTime);
} catch (InterruptedException e) {
//ignored
}
System.out.printf("%s 生产了 %d 耗时 %d 秒 %n", Thread.currentThread().getName(), randomInt, sleepTime);
return randomInt;
}, THREAD_POOL_EXECUTOR);
final long startMs = System.currentTimeMillis();
Integer result = CompletableFuture.supplyAsync(() -> {
int randomInt = RandomUtil.randomInt(1, 10);
final long sleepTime = 2;
try {
TimeUnit.SECONDS.sleep(sleepTime);
} catch (InterruptedException e) {
//ignored
}
System.out.printf("%s 生产了 %d 耗时 %d 秒%n", Thread.currentThread().getName(), randomInt, sleepTime);
return randomInt;
}, THREAD_POOL_EXECUTOR)
.thenCombineAsync(integerCompletableFuture, Integer::sum)
.get();
System.out.printf("获取最终结果耗时 %d 秒%n", (System.currentTimeMillis() - startMs) / 1000);
System.out.println("result = " + result);
}
/**
* 测试 CompletableFuture:
* <pre>
* {@code
* thenComposeAsync(
* Function<? super T, ? extends CompletionStage<U>> fn,
* Executor executor)
* }
* </pre>
*/
@Test
public void testThenComposeAsync() throws ExecutionException, InterruptedException {
final long startMs = System.currentTimeMillis();
Integer result = CompletableFuture.supplyAsync(() -> {
int randomInt = RandomUtil.randomInt(1, 10);
final long sleepTime = 5;
try {
TimeUnit.SECONDS.sleep(sleepTime);
} catch (InterruptedException e) {
//ignored
}
System.out.printf("%s 生产了 %d 耗时 %d 秒%n", Thread.currentThread().getName(), randomInt, sleepTime);
return randomInt;
}, THREAD_POOL_EXECUTOR)
.thenComposeAsync(integer ->
CompletableFuture.supplyAsync(() -> {
System.out.printf("%s 获取到值 %d%n", Thread.currentThread().getName(), integer);
int randomInt = RandomUtil.randomInt(1, 100);
final long sleepTime = 2;
try {
TimeUnit.SECONDS.sleep(sleepTime);
} catch (InterruptedException e) {
//ignored
}
System.out.printf("%s 生产了 %d 耗时 %d 秒%n", Thread.currentThread().getName(), randomInt, sleepTime);
return randomInt + integer;
}, THREAD_POOL_EXECUTOR))
.get();
System.out.printf("获取最终结果耗时 %d 秒%n", (System.currentTimeMillis() - startMs) / 1000);
System.out.println("result = " + result);
}
/**
* 测试 CompletableFuture:
* <pre>
* {@code
* thenAcceptBothAsync(
* CompletionStage<? extends U> other,
* BiConsumer<? super T, ? super U> action, Executor executor)
* }
* </pre>
*/
@Test
public void testThenAcceptBothAsync() throws ExecutionException, InterruptedException {
final long startMs = System.currentTimeMillis();
CompletableFuture.supplyAsync(() -> {
int randomInt = RandomUtil.randomInt(1, 10);
final long sleepTime = 5;
try {
TimeUnit.SECONDS.sleep(sleepTime);
} catch (InterruptedException e) {
//ignored
}
System.out.printf("%s 生产了 %d 耗时 %d 秒%n", Thread.currentThread().getName(), randomInt, sleepTime);
return randomInt;
}, THREAD_POOL_EXECUTOR)
.thenAcceptBothAsync(
CompletableFuture.supplyAsync(() -> {
int randomInt = RandomUtil.randomInt(1, 100);
final long sleepTime = 2;
try {
TimeUnit.SECONDS.sleep(sleepTime);
} catch (InterruptedException e) {
//ignored
}
System.out.printf("%s 生产了 %d 耗时 %d 秒%n", Thread.currentThread().getName(), randomInt, sleepTime);
return randomInt;
}), (result1, result2) -> System.out.printf("两个 CompletableFuture 和: %d%n", result1 + result2), THREAD_POOL_EXECUTOR)
.get();
System.out.printf("获取最终结果耗时 %d 秒%n", (System.currentTimeMillis() - startMs) / 1000);
}
/**
* 测试 CompletableFuture:
* <pre>
* {@code
* runAfterBothAsync(
* CompletionStage<?> other,
* Runnable action,
* Executor executor
* )
* }
* </pre>
*/
@Test
public void testRunAfterBothAsync() throws ExecutionException, InterruptedException {
final long startMs = System.currentTimeMillis();
CompletableFuture.supplyAsync(() -> {
int randomInt = RandomUtil.randomInt(1, 10);
final long sleepTime = 5;
try {
TimeUnit.SECONDS.sleep(sleepTime);
} catch (InterruptedException e) {
//ignored
}
System.out.printf("%s 生产了 %d 耗时 %d 秒%n", Thread.currentThread().getName(), randomInt, sleepTime);
return randomInt;
}, THREAD_POOL_EXECUTOR)
.runAfterBothAsync(
CompletableFuture.supplyAsync(() -> {
int randomInt = RandomUtil.randomInt(1, 100);
final long sleepTime = 2;
try {
TimeUnit.SECONDS.sleep(sleepTime);
} catch (InterruptedException e) {
//ignored
}
System.out.printf("%s 生产了 %d 耗时 %d 秒%n", Thread.currentThread().getName(), randomInt, sleepTime);
return randomInt;
}, THREAD_POOL_EXECUTOR), () -> System.out.printf("%s :两个 CompletableFuture运行耗时 %d 秒%n", Thread.currentThread().getName(), (System.currentTimeMillis() - startMs) / 1000)
, THREAD_POOL_EXECUTOR)
.get();
}
/**
* 测试 CompletableFuture:
* <pre>
* {@code
* applyToEitherAsync(
* CompletionStage<? extends T> other, Function<? super T, U> fn,
* Executor executor)
* }
* </pre>
*/
@Test
public void testApplyToEitherAsync() throws ExecutionException, InterruptedException {
final long startMs = System.currentTimeMillis();
Integer result = CompletableFuture.supplyAsync(() -> {
int randomInt = RandomUtil.randomInt(1, 10);
final long sleepTime = 6;
try {
TimeUnit.SECONDS.sleep(sleepTime);
} catch (InterruptedException e) {
//ignored
}
System.out.printf("%s 生产了 %d 耗时 %d 秒%n", Thread.currentThread().getName(), randomInt, sleepTime);
return randomInt;
}, THREAD_POOL_EXECUTOR)
.applyToEitherAsync(
CompletableFuture.supplyAsync(() -> {
int randomInt = RandomUtil.randomInt(1, 100);
final long sleepTime = 2;
try {
TimeUnit.SECONDS.sleep(sleepTime);
} catch (InterruptedException e) {
//ignored
}
System.out.printf("%s 生产了 %d 耗时 %d 秒%n", Thread.currentThread().getName(), randomInt, sleepTime);
return randomInt;
}, THREAD_POOL_EXECUTOR), (i) -> {
System.out.printf("%s :获取到结果: %d 等待耗时 %d 秒%n", Thread.currentThread().getName(), i, (System.currentTimeMillis() - startMs) / 1000);
final long sleepTime = 3;
try {
TimeUnit.SECONDS.sleep(sleepTime);
} catch (InterruptedException e) {
// ignored
}
System.out.printf("%s 处理结果耗时 %d 秒%n", Thread.currentThread().getName(), sleepTime);
return i + 100;
}
, THREAD_POOL_EXECUTOR)
.get();
System.out.printf("获取到最终结果: %d, 耗时 %d 秒 %n", result, (System.currentTimeMillis() - startMs) / 1000);
}
/**
* 测试 CompletableFuture:
* <pre>
* {@code
* acceptEitherAsync(
* CompletionStage<? extends T> other, Consumer<? super T> action,
* Executor executor)
* }
* </pre>
*/
@Test
public void testAcceptEitherAsync() throws ExecutionException, InterruptedException {
final long startMs = System.currentTimeMillis();
CompletableFuture.supplyAsync(() -> {
int randomInt = RandomUtil.randomInt(1, 10);
final long sleepTime = 5;
try {
TimeUnit.SECONDS.sleep(sleepTime);
} catch (InterruptedException e) {
//ignored
}
System.out.printf("%s 生产了 %d 耗时 %d 秒%n", Thread.currentThread().getName(), randomInt, sleepTime);
return randomInt;
}, THREAD_POOL_EXECUTOR)
.acceptEitherAsync(
CompletableFuture.supplyAsync(() -> {
int randomInt = RandomUtil.randomInt(1, 100);
final long sleepTime = 2;
try {
TimeUnit.SECONDS.sleep(sleepTime);
} catch (InterruptedException e) {
//ignored
}
System.out.printf("%s 生产了 %d 耗时 %d 秒%n", Thread.currentThread().getName(), randomInt, sleepTime);
return randomInt;
}, THREAD_POOL_EXECUTOR), (result) -> System.out.printf("%s :获取到结果: %d 等待耗时 %d 秒%n", Thread.currentThread().getName(), result, (System.currentTimeMillis() - startMs) / 1000)
, THREAD_POOL_EXECUTOR)
.get();
}
/**
* 测试 CompletableFuture:
* <pre>
* {@code
* exceptionally(
* Function<Throwable, ? extends T> fn)
* }
* </pre>
*/
@Test
public void testExceptionally() throws ExecutionException, InterruptedException {
String result = CompletableFuture.
supplyAsync(() -> "成功响应",
THREAD_POOL_EXECUTOR)
.exceptionally(Throwable::getMessage)
.get();
System.out.printf("成功响应结果: %s%n", result);
result = CompletableFuture.
supplyAsync(() -> {
if (true) {
throw new RuntimeException("测试 exceptional 异常补偿");
}
return "成功响应";
},
THREAD_POOL_EXECUTOR)
.exceptionally(Throwable::getMessage)
.get();
System.out.printf("失败响应结果: %s%n", result);
}
/**
* 测试 CompletableFuture:
* <pre>
* {@code
* whenCompleteAsync(
* BiConsumer<? super T, ? super Throwable> action, Executor executor)
* }
* </pre>
*/
@Test
public void testWhenCompeteAsync() throws ExecutionException, InterruptedException {
String result = CompletableFuture.supplyAsync(() -> "dankal", THREAD_POOL_EXECUTOR)
.whenCompleteAsync((str, exception) -> {
if (StrUtil.isNotBlank(str)) {
System.out.println("响应结果: " + str);
}
if (null != exception) {
System.out.println("发生异常: " + exception.getMessage());
}
}, THREAD_POOL_EXECUTOR)
.get();
System.out.println("result = " + result);
result = CompletableFuture.supplyAsync(() -> {
if (true) {
throw new RuntimeException("just throw a exception");
}
return "dankal";
}, THREAD_POOL_EXECUTOR)
.whenCompleteAsync((str, exception) -> {
if (StrUtil.isNotBlank(str)) {
System.out.println("响应结果: " + str);
}
if (null != exception) {
System.out.println("发生异常: " + exception.getMessage());
}
}, THREAD_POOL_EXECUTOR)
.get();
System.out.println("result = " + result);
}
/**
* 测试 CompletableFuture:
* <pre>
* {@code
* handleAsync(
* BiFunction<? super T, Throwable, ? extends U> fn, Executor executor)
* }
* </pre>
*/
@Test
public void testHandlerAsync() throws ExecutionException, InterruptedException {
String resultStr = CompletableFuture.supplyAsync(() -> "success", THREAD_POOL_EXECUTOR)
.handleAsync((res, exception) -> {
if (StrUtil.isNotBlank(res)) {
return res;
}
return exception.getMessage();
}, THREAD_POOL_EXECUTOR)
.get();
System.out.println("resultStr = " + resultStr);
resultStr = CompletableFuture.supplyAsync(() -> {
if (true) {
throw new RuntimeException("a exception happened");
}
return "success";
}, THREAD_POOL_EXECUTOR)
.handleAsync((res, exception) -> {
if (StrUtil.isNotBlank(res)) {
return res;
}
return exception.getMessage();
}, THREAD_POOL_EXECUTOR)
.get();
System.out.println("resultStr = " + resultStr);
}
/**
* 关闭 线程池资源
*/
@AfterAll
public static void after() {
THREAD_POOL_EXECUTOR.shutdown();
}
}
package cn.dankal.share.util.completablefuture;
import cn.dankal.share.common.ThreadExecutorFactory;
import cn.hutool.core.util.RandomUtil;
import org.junit.jupiter.api.AfterAll;
import org.junit.jupiter.api.Test;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
/**
* @author ZGH.MercyModest
* @version V1.0.0
* @create 2021-01-02
*/
public class CompletableFutureResultGetTest {
/**
* ThreadPoolExecutor
*/
private final static ThreadPoolExecutor THREAD_POOL_EXECUTOR = ThreadExecutorFactory.getThreadPoolExecutor("completableFuture-result-get-test");
/**
* completableFuture 结果获取: get()
*/
@Test
public void testCompletableFutureGet() {
// 使用 get() 获取 completableFuture 运行结果
CompletableFuture<Integer> completableFuture = CompletableFuture.supplyAsync(() -> {
int result = RandomUtil.randomInt(1, 100);
System.out.printf("线程 %s 已生产随机数 %d%n", Thread.currentThread().getName(), result);
return result;
}, THREAD_POOL_EXECUTOR);
try {
System.out.printf("%s 线程 即将开始获取 completableFuture 结果%n", Thread.currentThread().getName());
Integer result = completableFuture.get();
System.out.println(result);
} catch (Exception exception) {
exception.printStackTrace();
}
}
/**
* completableFuture 结果获取: get(long timeout, TimeUnit unit)
*/
@Test
public void testCompletableFutureGetTimeout() {
// 使用 get() 获取 completableFuture 运行结果
CompletableFuture<Integer> completableFuture = CompletableFuture.supplyAsync(() -> {
int result = RandomUtil.randomInt(1, 100);
System.out.printf("线程 %s 已生产随机数 %d%n", Thread.currentThread().getName(), result);
try {
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
// ignored
}
return result;
}, THREAD_POOL_EXECUTOR);
try {
System.out.printf("%s 线程 即将开始获取 completableFuture 结果%n", Thread.currentThread().getName());
Integer result = completableFuture.get(100, TimeUnit.MILLISECONDS);
System.out.println(result);
} catch (Exception exception) {
System.out.println(exception.getMessage());
}
}
/**
* completableFuture 结果获取: getNow(T valueIfAbsent)
*/
@Test
public void testCompletableFutureGetNow() {
// 使用 get() 获取 completableFuture 运行结果
CompletableFuture<Integer> completableFuture = CompletableFuture.supplyAsync(() -> {
int result = RandomUtil.randomInt(1, 100);
System.out.printf("线程 %s 已生产随机数 %d%n", Thread.currentThread().getName(), result);
try {
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
// ignored
}
return result;
}, THREAD_POOL_EXECUTOR);
try {
System.out.printf("%s 线程 即将开始获取 completableFuture 结果%n", Thread.currentThread().getName());
Integer result = completableFuture.getNow(-999);
System.out.println(result);
} catch (Exception exception) {
System.out.println(exception.getMessage());
}
}
/**
* completableFuture 结果获取: join()
*/
@Test
public void testCompletableFutureJoin() {
// 使用 get() 获取 completableFuture 运行结果
CompletableFuture<Integer> completableFuture = CompletableFuture.supplyAsync(() -> {
int result = RandomUtil.randomInt(1, 100);
System.out.printf("线程 %s 已生产随机数 %d%n", Thread.currentThread().getName(), result);
try {
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
// ignored
}
return result;
}, THREAD_POOL_EXECUTOR);
try {
System.out.printf("%s 线程 即将开始获取 completableFuture 结果%n", Thread.currentThread().getName());
Integer result = completableFuture.join();
System.out.println(result);
} catch (Exception exception) {
System.out.println(exception.getMessage());
}
}
/**
* 关闭 线程池资源
*/
@AfterAll
public static void after() {
THREAD_POOL_EXECUTOR.shutdown();
}
}
package cn.dankal.share.util.completablefuture;
import cn.dankal.share.common.ThreadExecutorFactory;
import org.junit.jupiter.api.Test;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
import java.util.function.Supplier;
/**
* <p>
* <code>CompletableFuture</code> 测试
* </p>
*
* @author ZGH.MercyModest
* @version V1.0.0
* @create 2021-01-22
*/
public class CompletableFutureTest {
/*
需求:
- 获取商品信息 (A) 耗时 120 毫秒
- 获取商品价格 (B) 耗时 95 毫秒
- 商品属性获取并归并(Sku/Spu) (C) 耗时 165 毫秒
重要: 我们只有获取到了 '商品信息' 才能执行 '商品属性获取并归并',即 C 必须在 A 之后执行
*/
/**
* <code>CompletableFuture</code> 测试
*/
@Test
public void test() throws Exception {
ThreadPoolExecutor threadPoolExecutor = ThreadExecutorFactory.getThreadPoolExecutor("dankal-test-completableFuture");
// 获取商品信息 耗时: 120 毫秒
final long getProductElapsed = 120L;
Supplier<Integer> getProductSupplier = () -> {
try {
// do work
TimeUnit.MILLISECONDS.sleep(getProductElapsed);
} catch (InterruptedException e) {
// ignored
}
// 返回商品信息成功: 200
System.out.printf("%s 获取商品信息成功,即将返回%n", Thread.currentThread().getName());
return 200;
};
// 获取商品价格 耗时 95 毫秒
final long getProductPriceElapsed = 95L;
Supplier<Double> getProductPriceSupplier = () -> {
try {
// do work
TimeUnit.MILLISECONDS.sleep(getProductPriceElapsed);
} catch (InterruptedException e) {
// ignored
}
System.out.printf("%s 成功获取到商品价格即将开始返回%n", Thread.currentThread().getName());
return 135.53;
};
// 归并商品属性耗时 165 毫秒
final long getProductPropertiesElapsed = 165L;
//程序开始计时
final long startMs = System.currentTimeMillis();
// 获取 商品信息 并归并商品属性
CompletableFuture<Integer> productAboutCompletableFuture = CompletableFuture.supplyAsync(getProductSupplier, threadPoolExecutor)
.thenApplyAsync(previousResult -> {
System.out.printf("%s 获取到商品信息: %d 工耗时 %d 毫秒%n", Thread.currentThread().getName(), previousResult, (System.currentTimeMillis() - startMs));
try {
// do work
TimeUnit.MILLISECONDS.sleep(getProductPropertiesElapsed);
} catch (InterruptedException e) {
// ignored
}
System.out.printf("%s 完成商品属性归并,即将返回结果%n", Thread.currentThread().getName());
return previousResult * 2;
}, threadPoolExecutor);
CompletableFuture<Double> getProductPriceCompletableFuture = CompletableFuture.supplyAsync(getProductPriceSupplier, threadPoolExecutor);
// 等待程序执行完成
CompletableFuture.allOf(productAboutCompletableFuture, getProductPriceCompletableFuture).get();
System.out.println("程序执行完成:");
System.out.printf("\t运行耗时: %d %n", (System.currentTimeMillis() - startMs));
System.out.printf("\t理论耗时: %d %n", (getProductElapsed + getProductPropertiesElapsed + getProductPriceElapsed));
// 模拟 Service 方法返回
System.out.printf("获取商品详情: %d%n", productAboutCompletableFuture.get()/2);
System.out.printf("获取商品属性: %d%n", productAboutCompletableFuture.get());
System.out.printf("获取商品价格: %.2f RMB%n", getProductPriceCompletableFuture.get());
}
}
# 蛋壳创意科技-技术分享-Java常用并发工具类(笔记版)
# 蛋壳创意科技-技术分享-Java常用并发工具类(笔记版)
## `Fork-Join`
<b> java 下多线程的开发可以我们自己启用多线程,线程池,还可以使用forkjoin,
forkjoin 可以让我们不去了解诸如Thread,Runnable 等相关的知识,只要遵循
forkjoin 的开发模式,就可以写出很好的多线程并发程序</b>
### 什么是分而治之
> ​ 同时forkjoin 在处理某一类问题时非常的有用,哪一类问题?分而治之的问
> 题。
>
> 十大计算机经典算法
>
> - 快速排序
>
> - 堆排序
>
> - 归并排序
>
> - 二分查找
>
> - 线性查找、
> 深度优先
>
> - 广度优先
>
> - Dijkstra
>
> - 动态规划
>
> - 朴素贝叶斯分类
>
> 有几个属于分而治之?
>
> ​ 3 个
>
> - 快速排序
>
> - 归并排序
>
> - 二分查找
>
> 分治法的设计思想是:将一个难以直接解决的大问题,分割成一些规模较小的相同问题,以便各个击破,分而治之。
>
> ​ 分治策略是:对于一个规模为n 的问题,若该问题可以容易地解决(比如说规模n 较小)则直接解决,否则将其分解为k 个规模较小的子问题,这些子问题互相独立且与原问题形式相同(子问题相互之间有联系就会变为动态规范算法),递归地解这些子问题,然后将各子问题的解合并得到原问题的解。这种算法设计策略叫做分治法。
#### 简单实现 插入排序
> ##### 什么是插入排序
>
> ​ 插入排序属于内部排序,是对于与排序元素以插入方式寻找到合适的位置,以达到排序的目的
>
> ##### 插入排序思想
>
> ​ 插入排序(Insertion Sorting)的基本思想是:把n 个待排序的元素看成为一个有序表和一个无序表,开始时有序表中只包含一个元素,无序表中包含有n-1 个元素,排序过程中每次从无序表中取出第一个元素,把它的排序码依次与有序表元素的排序码进行比较,将它插入到有序表中的适当位置,使之成为新的有序表。
#### 浅谈归并排序
​ 归并排序是建立在归并操作上的一种有效的排序算法。该算法是采用分治法
的一个非常典型的应用。将已有序的子序列合并,得到完全有序的序列;即先使
每个子序列有序,再使子序列段间有序。若将两个有序表合并成一个有序表,称为2-路归并,与之对应的还有多路归并。
​ 对于给定的一组数据,利用递归与分治技术将数据序列划分成为越来越小的半子表,在对半子表排序后,再用递归方法将排好序的半子表合并成为越来越大的有序序列。为了提升性能,有时我们在半子表的个数小于某个数(比如15)的情况下,对半子表的排序采用其他排序算法,比如插入排序。
![](https://img.mercymodest.com/public/20201228180221.png)
![](https://img.mercymodest.com/public/20201228180420.png)
### `Fork-Join`原理
![](https://img.mercymodest.com/public/20210112142502.jpg)
#### 工作密取
​ 即当前线程的Task 已经全被执行完毕,则自动取到其他线程的Task 池中取
出Task 继续执行。
​ ForkJoinPool 中维护着多个线程(一般为CPU 核数)在不断地执行Task,每
个线程除了执行自己职务内的Task 之外,还会根据自己工作线程的闲置情况去
获取其他繁忙的工作线程的Task,如此一来就能能够减少线程阻塞或是闲置的时
间,提高CPU 利用率。
![](https://img.mercymodest.com/public/20210112142618.jpg)
### `Fork-Join`编码实现
#### `Fork-Join`使用的标准范式
> ​ 我们要使用ForkJoin 框架,必须首先创建一个ForkJoin 任务。它提供在任务
> 中执行fork 和join 的操作机制,通常我们不直接继承ForkjoinTask 类,只需要直
> 接继承其子类。
>
> 1. RecursiveAction,用于没有返回结果的任务
> 2. RecursiveTask,用于有返回值的任务
> task 要通过ForkJoinPool 来执行,使用submit 或invoke 提交,两者的区
> 别是:invoke 是同步执行,调用之后需要等待任务完成,才能执行后面的代码;
> submit 是异步执行。
> join()和get 方法当任务完成的时候返回计算结果。
![](https://img.mercymodest.com/public/20210112142903.jpg)
​ 在我们自己实现的compute 方法里,首先需要判断任务是否足够小,如果足够小就直接执行任务。如果不足够小,就必须分割成两个子任务,每个子任务在调用invokeAll 方法时,又会进入compute 方法,看看当前子任务是否需要继续分割成孙任务,如果不需要继续分割,则执行当前子任务并返回结果。使用join方法会等待子任务执行完并得到其结果。
### 自定义工具类介绍
#### `IntArrayFactory.java`
**int 数组工厂**
```java
package cn.dankal.share.common;
import cn.hutool.core.util.RandomUtil;
/**
* int 数组工厂
* <p>
* 生成指定长度的 int 数组 并使用随机数填充
* <br/>
* 随机数范围: array.length*3
* </p>
*
* @author ZGH.MercyModest
* @version V1.0.0
* @create 2021-01-02
*/
public class IntArrayFactory {
/**
* 生成的默认数组长度
*/
private final static Integer DEFAULT_ARRAY_LENGTH = 1000000;
/**
* 生成默认长度的 int 数组
*
* @return int []
* @see IntArrayFactory#DEFAULT_ARRAY_LENGTH
*/
public static int[] generateIntArray() {
return generateIntArray(DEFAULT_ARRAY_LENGTH);
}
/**
* 生成指定长度的数组
*
* @param arrayLength 生成长度为 <code>arrayLength</code> int 数组
* @return int []
*/
public static int[] generateIntArray(int arrayLength) {
if (arrayLength <= 0) {
arrayLength = DEFAULT_ARRAY_LENGTH;
}
int[] resultArray = new int[arrayLength];
for (int i = 0; i < resultArray.length; i++) {
resultArray[i] = RandomUtil.randomInt(0, arrayLength * 3);
}
return resultArray;
}
}
```
#### `IntArraySumUtil.java`
**int 数组 求和工具类**
```java
package cn.dankal.share.common;
import java.util.concurrent.TimeUnit;
/**
* int 数组 求和工具类
*
* @author ZGH.MercyModest
* @version V1.0.0
* @create 2021-01-02
*/
public class IntArraySumUtil {
/**
* 数组求和
*
* @param targetArray 目标数组
* @param fromIndex from index
* @param toIndex to index
* @return 计算结果
*/
public static int sumArray(int[] targetArray, int fromIndex, int toIndex) {
if (toIndex < fromIndex) {
throw new IllegalArgumentException("fromIndex 必须小于等于 toIndex");
}
if (targetArray.length <= toIndex) {
throw new IllegalArgumentException("toIndex 超出 数组长度范围");
}
int result = 0;
for (int i = fromIndex; i <= toIndex; i++) {
try {
// TODO 休眠 100 毫秒 便于区分不同计算方式和 fork/join 的区别
TimeUnit.MILLISECONDS.sleep(100);
} catch (InterruptedException e) {
// ignore
}
result += targetArray[i];
}
return result;
}
}
```
#### `MergeSort.java`
**归并排序简单实现**
```java
package cn.dankal.share.common;
import java.util.Arrays;
/**
* 归并排序简单实现
*
* @author ZGH.MercyModest
* @version V1.0.0
* @create 2021-01-02
*/
public class MergeSort {
/**
* 数组拆分默认临界值
*/
public final static int DEFAULT_ARRAY_SPILT_THRESHOLD = 47;
/**
* 归并排序
*
* @param numArray 需要归并排序的数组
* @return 排序好后的数组
*/
public static int[] sort(int[] numArray) {
if (numArray.length <= DEFAULT_ARRAY_SPILT_THRESHOLD) {
return InsertionSort.doAscSort(numArray);
} else {
/*切分数组,然后递归调用*/
int mid = numArray.length / 2;
int[] left = Arrays.copyOfRange(numArray, 0, mid);
int[] right = Arrays.copyOfRange(numArray, mid, numArray.length);
return mergeSortedArray(sort(left), sort(right));
}
}
/**
* 将两个有序数组合并为一个有序数组(顺序排序)
*
* @param leftArray left array
* @param rightArray right array
* @return 排好序后合并的数组
*/
public static int[] mergeSortedArray(int[] leftArray, int[] rightArray) {
int[] result = new int[leftArray.length + rightArray.length];
for (int index = 0, i = 0, j = 0; index < result.length; index++) {
if (i >= leftArray.length) {
// 左边数组已经取完,完全取右边数组的值即可
result[index] = rightArray[j++];
} else if (j >= rightArray.length) {
// 右边数组已经取完,完全取左边数组的值即可
result[index] = leftArray[i++];
} else if (leftArray[i] > rightArray[j]) {
// 左边数组的元素值大于右边数组,取右边数组的值
result[index] = rightArray[j++];
} else {
// 右边数组的元素值大于左边数组,取左边数组的值
result[index] = leftArray[i++];
}
}
return result;
}
}
```
#### `InsertionSort.java`
**插入排序简单实现**
```java
package cn.dankal.share.common;
/**
* 插入排序简单实现
*
* @author ZGH.MercyModest
* @version V1.0.0
* @create 2021-01-02
*/
public class InsertionSort {
/**
* 排序方式
*/
public enum Sort {
/**
* 倒序排序
*/
DESC,
/**
* 顺序排序
*/
ASC
}
/**
* 使用插入排序顺序排序目标数组
*
* @param unSortedArray 未排序的数组
* @return int[] 已经排序的数组
*/
public static int[] doAscSort(int[] unSortedArray) {
return doSort(unSortedArray, Sort.ASC);
}
/**
* 使用插入排序倒序排序目标数组
*
* @param unSortedArray 未排序的数组
* @return int[] 已经排序的数组
*/
public static int[] doDescSort(int[] unSortedArray) {
return doSort(unSortedArray, Sort.DESC);
}
/**
* int 数组排序
*
* @param unSortedArray 待排序的数组
* @param sortType 排序类型
* @return 排序好的数组
* @see Sort
*/
public static int[] doSort(int[] unSortedArray, Sort sortType) {
if (unSortedArray.length == 0) {
return unSortedArray;
}
// 默认是 顺序排序
boolean asc = true;
if (Sort.DESC.toString().equalsIgnoreCase(sortType.toString())) {
// 倒序排序
asc = false;
}
// 带排序元素
// 带排序元素前面的元素已经全部排序
int currentValue;
for (int i = 0; i < unSortedArray.length - 1; i++) {
// 已被排序数据的索引
int preIndex = i;
currentValue = unSortedArray[preIndex + 1];
//在已经排序好的元素面中寻找到当前元素合适的位置
while (preIndex >= 0 && (asc ? currentValue < unSortedArray[preIndex] : currentValue > unSortedArray[preIndex])) {
// 从已排序元素开始,将其与带排序元素逐一比较,以寻找带排序元素的合适位置
unSortedArray[preIndex + 1] = unSortedArray[preIndex];
preIndex--;
}
// while 循环结束时,说明已经找到了当前待排序数据的合适位置,插入
unSortedArray[preIndex + 1] = currentValue;
}
return unSortedArray;
}
}
```
#### `ThreadExecutorFactory.java`
**ThreadExecutor 简单工厂**
```java
package cn.dankal.share.common;
import cn.hutool.core.util.StrUtil;
import java.util.concurrent.ArrayBlockingQueue;
import java.util.concurrent.ThreadFactory;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
/**
* ThreadExecutor 工厂
*
* @author ZGH.MercyModest
* @version V1.0.0
* @create 2021-01-02
*/
public class ThreadExecutorFactory {
/**
* 获取 ThreadPoolExecutor
*
* @param corePoolSize 核心线程数
* @param maximumPoolSize 最大线程数
* @param keepAliveTime 超过核心线程数的线程存活时间
* @param timeUnit 超过核心线程数的线程存活时间单位
* @param threadNamePrefix 创建线程的名称前缀
* @param blockQueueCapacity 线程池阻塞队列的容量
* @return ThreadPoolExecutor
* @see ThreadPoolExecutor
*/
public static ThreadPoolExecutor getThreadPoolExecutor(int corePoolSize,
int maximumPoolSize,
long keepAliveTime,
TimeUnit timeUnit,
String threadNamePrefix,
int blockQueueCapacity) {
// 参数校验
if (corePoolSize <= 0) {
throw new IllegalArgumentException("corePoolSize 必须大于0");
}
if (maximumPoolSize <= 0) {
throw new IllegalArgumentException("maximumPoolSize 必须大于0");
}
if (keepAliveTime < 0) {
throw new IllegalArgumentException("keepAliveTime 必须大于0");
}
if (null == timeUnit) {
throw new IllegalArgumentException("timeUnit 不能为空");
}
if (corePoolSize > maximumPoolSize) {
throw new IllegalArgumentException("corePoolSize 必须小于等于 maximumPoolSize");
}
if (StrUtil.isBlank(threadNamePrefix)) {
throw new IllegalArgumentException("threadNamePrefix 不能为空");
}
if (blockQueueCapacity <= 0) {
throw new IllegalArgumentException("blockQueueCapacity 必须大于0");
}
return new ThreadPoolExecutor(
corePoolSize,
maximumPoolSize,
keepAliveTime,
timeUnit,
new ArrayBlockingQueue<>(blockQueueCapacity),
threadFactory(threadNamePrefix)
);
}
/**
* ThreadFactory 简答实现 --> 为线程池线程指定名称
*
* @param threadNamePrefix 线程池线程名称前缀
* @return ThreadFactory
*/
private static ThreadFactory threadFactory(final String threadNamePrefix) {
return (runnable -> {
Thread thread = new Thread(runnable);
long id = thread.getId();
thread.setName(threadNamePrefix + " - " + id);
return thread;
});
}
/**
* 获取一个 <code>ThreadPoolExecutor</code>
* <p>
* coreSize: 6
* <br/>
* maximumPoolSize: 10
* <br/>
* KeepAliveTime: 10
* <br/>
* unit: TimeUnit.MILLISECONDS
* <br/>
* blockQueueCapacity: 100
* <br/>
* </p>
*
* @param threadNamePrefix <code>threadNamePrefix</code>
* @return ThreadPoolExecutor
*/
public static ThreadPoolExecutor getThreadPoolExecutor(final String threadNamePrefix) {
return ThreadExecutorFactory.getThreadPoolExecutor(6,
10,
10,
TimeUnit.MILLISECONDS,
threadNamePrefix,
100);
}
}
```
### 自定义工具可用性测试
#### 测试 插入排序
##### 单元测试
```java
/**
* 测试 插入排序
*/
@Test
public void testInsertSort() {
int[] generateIntArray = IntArrayFactory.generateIntArray(20);
System.out.println("排序前:");
System.out.println(Arrays.toString(generateIntArray));
System.out.println("========================");
InsertionSort.doAscSort(generateIntArray);
System.out.println("顺序排序后:");
System.out.println(Arrays.toString(generateIntArray));
System.out.println("倒序排序后:");
InsertionSort.doDescSort(generateIntArray);
System.out.println(Arrays.toString(generateIntArray));
System.out.println("========================");
}
```
##### 运行结果
![](https://img.mercymodest.com/public/20210113113913.png)
#### 测试 归并排序(顺序排序)
##### 单元测试
```java
/**
* 测试 MergeSort
*/
@Test
public void testMergeSort() {
int[] generateIntArray = IntArrayFactory.generateIntArray(25);
System.out.println("排序前:");
System.out.println(Arrays.toString(generateIntArray));
System.out.println("========================");
MergeSort.sort(generateIntArray);
System.out.println("排序后:");
System.out.println(Arrays.toString(generateIntArray));
System.out.println("========================");
}
```
##### 运行结果
![](https://img.mercymodest.com/public/20210113114042.png)
### `ForkJoin: RecursiveTask<V>`
**任务有返回值**
#### **使用ForkJoin简单实现归并排序.**
```java
package cn.dankal.share.forkjoin;
import cn.dankal.share.common.InsertionSort;
import cn.dankal.share.common.MergeSort;
import java.util.Arrays;
import java.util.concurrent.RecursiveTask;
/**
* 基于 {@link RecursiveTask} (ForkJoin) 简单实现 数组排序(归并排序)
*
* @author ZGH.MercyModest
* @version V1.0.0
* @create 2021-01-02
*/
public class IntArraySortTask extends RecursiveTask<int[]> {
/**
* 需要排序的数组
*/
private final int[] intArray;
/**
* 数组拆分临界值(length)
*/
private final int thresholdLength;
public IntArraySortTask(int[] intArray, int thresholdLength) {
this.intArray = intArray;
this.thresholdLength = thresholdLength;
}
@Override
protected int[] compute() {
if (intArray.length <= thresholdLength) {
// 达到拆分临界 直接排序
return InsertionSort.doAscSort(intArray);
} else {
// formIndex ... middle ... toIndex
// 任务拆分 将排序任务拆分成两个或更多
// 此处演示 拆分为 左右两个任务
int middle = intArray.length / 2;
// 将目标数组从 “中间”拆分
int[] leftIntArray = Arrays.copyOfRange(intArray, 0, middle);
int[] rightIntArray = Arrays.copyOfRange(intArray, middle, intArray.length);
// 拆分后,分为 左,右两个子Task
IntArraySortTask leftSortTask = new IntArraySortTask(leftIntArray, thresholdLength);
IntArraySortTask rightSortTask = new IntArraySortTask(rightIntArray, thresholdLength);
//执行所有 Task
invokeAll(leftSortTask, rightSortTask);
// 归并 left 的结果
int[] leftArray = leftSortTask.join();
// 归并 right 的结果
int[] rightArray = rightSortTask.join();
// 合并 归并结果
return MergeSort.mergeSortedArray(leftArray, rightArray);
}
}
}
```
###### 单元测试
```java
/**
* 测试 Fork/Join 实现的 归并排序
*/
@Test
public void testMergeSortByForkJoin() {
int[] generateIntArray = IntArrayFactory.generateIntArray(25);
System.out.println("排序前:");
System.out.println(Arrays.toString(generateIntArray));
System.out.println("========================");
ForkJoinPool forkJoinPool = new ForkJoinPool();
IntArraySortTask intArraySortTask = new IntArraySortTask(generateIntArray, 2);
int[] invokeResult = forkJoinPool.invoke(intArraySortTask);
System.out.println("排序后:");
System.out.println(Arrays.toString(invokeResult));
System.out.println("========================");
}
```
###### 运行结果
![](https://img.mercymodest.com/public/20210113114620.png)
##### ForkJoin 性能评测: 归并排序实现
**对比对象:**
- **通常归并排序**
- **Arrays.sort()**:DualPivotQuicksort 快速排序算法
**测试条件**
`3000000` int 数据 进行排序
###### 单元测试
```java
/**
* 测试 归并排序对比
*/
@Test
public void testMergeSortThreeWays() {
int[] generateIntArray = IntArrayFactory.generateIntArray(3000000);
System.out.println("数组元素: " + generateIntArray.length);
// general merge sort
final long generalMergeSortStart = System.currentTimeMillis();
MergeSort.sort(generateIntArray);
System.out.println("运行耗时: " + (System.currentTimeMillis() - generalMergeSortStart) + " 毫秒 (general mergeSort)");
// jdk utils: Arrays#sort
final long arraySortStartMs = System.currentTimeMillis();
// DualPivotQuicksort 快排
Arrays.sort(generateIntArray);
System.out.println("运行耗时: " + (System.currentTimeMillis() - arraySortStartMs) + " 毫秒 (Arrays#sort)");
// Fork/Join merge sort
ForkJoinPool forkJoinPool = new ForkJoinPool();
IntArraySortTask intArraySortTask = new IntArraySortTask(generateIntArray, 300);
final long forkJoinStartMs = System.currentTimeMillis();
forkJoinPool.invoke(intArraySortTask);
System.out.println("运行耗时: " + (System.currentTimeMillis() - forkJoinStartMs) + " 毫秒 (Fork/Join mergeSort)");
}
```
###### 运行结果
![](https://img.mercymodest.com/public/20210113115039.png)
#### 使用FrokJoin简单实现数组求和: ForkJoin 性能测试
##### 前置条件
**统一使用此方法对`Int`数组进行元素求和**
`IntArraySumUtil.java`
```java
/**
* 数组求和
*
* @param targetArray 目标数组
* @param fromIndex from index
* @param toIndex to index
* @return 计算结果
*/
public static int sumArray(int[] targetArray, int fromIndex, int toIndex) {
if (toIndex < fromIndex) {
throw new IllegalArgumentException("fromIndex 必须小于等于 toIndex");
}
if (targetArray.length <= toIndex) {
throw new IllegalArgumentException("toIndex 超出 数组长度范围");
}
int result = 0;
for (int i = fromIndex; i <= toIndex; i++) {
try {
// TODO 休眠 100 毫秒 便于区分不同计算方式和 fork/join 的区别
TimeUnit.MILLISECONDS.sleep(100);
} catch (InterruptedException e) {
// ignore
}
result += targetArray[i];
}
return result;
}
```
##### 单元测试
```java
/**
* 测试: Fork/Join的优势
*/
@Test
public void testAdvantageOfForkJoin() {
Map<String, Long> expenditureTimeMap = new LinkedHashMap<>(1 << 4);
final String streamDataKey = "java8 stream";
final String forkJoinDataKey = "fork join";
final String generalSumDataKey = "general sum";
final int count = 10;
for (int i = 0; i < count; i++) {
int[] generateIntArray = IntArrayFactory.generateIntArray(RandomUtil.randomInt(50, 100));
System.out.printf("目标数组 length: %d%n ", generateIntArray.length);
System.out.println(Arrays.toString(generateIntArray));
System.out.printf("============= 第 %d 次计算 =============%n", (i + 1));
final long streamStartMs = System.currentTimeMillis();
int result = Arrays.stream(generateIntArray)
.parallel()
.reduce(ForkJoinTest::intSum)
.orElse(-1);
long streamExpenditure = System.currentTimeMillis() - streamStartMs;
System.out.println("(java8 stream) 运行结果: " + result);
System.out.println("(java8 stream) 运行耗时: " + streamExpenditure + " 毫秒\n");
// 将 java8 stream 每次执行时间存储在 LinkedHashMap 中
if (!expenditureTimeMap.containsKey(streamDataKey)) {
expenditureTimeMap.put(streamDataKey, streamExpenditure);
} else {
Long oldExpenditure = expenditureTimeMap.get(streamDataKey);
expenditureTimeMap.put(streamDataKey, oldExpenditure + streamExpenditure);
}
IntArraySumTask intArraySumTask = new IntArraySumTask(10, generateIntArray, 0, generateIntArray.length - 1);
ForkJoinPool forkJoinPool = new ForkJoinPool();
final long forkJoinStartMs = System.currentTimeMillis();
Integer invokeResult = forkJoinPool.invoke(intArraySumTask);
long forkJoinExpenditure = System.currentTimeMillis() - forkJoinStartMs;
System.out.println("(fork/join) 运行结果: " + invokeResult);
System.out.println("(fork/join) 运行耗时: " + forkJoinExpenditure + " 毫秒\n");
// 将 fork/join 每次执行时间存储在 LinkedHashMap 中
if (!expenditureTimeMap.containsKey(forkJoinDataKey)) {
expenditureTimeMap.put(forkJoinDataKey, forkJoinExpenditure);
} else {
Long oldExpenditure = expenditureTimeMap.get(forkJoinDataKey);
expenditureTimeMap.put(forkJoinDataKey, oldExpenditure + forkJoinExpenditure);
}
final long generalStartMs = System.currentTimeMillis();
int sum = IntArraySumUtil.sumArray(generateIntArray, 0, generateIntArray.length - 1);
long generalSumExpenditure = System.currentTimeMillis() - generalStartMs;
System.out.println("(general sum) 运行结果: " + sum);
System.out.println("(general sum) 运行耗时: " + generalSumExpenditure + " 毫秒");
// 将 general sum 每次执行时间存储在 LinkedHashMap 中
if (!expenditureTimeMap.containsKey(generalSumDataKey)) {
expenditureTimeMap.put(generalSumDataKey, generalSumExpenditure);
} else {
Long oldExpenditure = expenditureTimeMap.get(generalSumDataKey);
expenditureTimeMap.put(generalSumDataKey, oldExpenditure + generalSumExpenditure);
}
System.out.println("=============================\n");
}
System.out.println("平均耗时");
for (String key : expenditureTimeMap.keySet()) {
System.out.printf("%s 运行 %d 次,平均耗时 %d 毫秒%n", key, count, expenditureTimeMap.get(key) / count);
}
}
```
##### 运行结果
**次要运行结果部分截图**
![](https://img.mercymodest.com/public/20210113144637.png)
**主要运行结果截图**
![](https://img.mercymodest.com/public/20210113144642.png)
### `ForkJoin:RecursiveAction`
**任务无返回值**
#### 使用 Fork/Join 框架实现目标文件搜索
##### `FindFileTask.java`
```java
package cn.dankal.share.forkjoin;
import cn.hutool.core.collection.CollectionUtil;
import cn.hutool.core.io.FileUtil;
import cn.hutool.core.util.ArrayUtil;
import java.io.File;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.RecursiveAction;
import java.util.concurrent.atomic.AtomicInteger;
/**
* 基于 {@link RecursiveAction} (ForkJoin)实现 文件查找与简单统计
*
* @author ZGH.MercyModest
* @version V1.0.0
* @create 2021-01-02
*/
public class FindFileTask extends RecursiveAction {
/**
* 目标文件夹
*/
private final String targetFolder;
/**
* 目标文件后缀
*/
private final String findFileSuffix;
/**
* 目标文件计数器
*/
public final static AtomicInteger COUNTER_TARGET_FILE = new AtomicInteger(0);
/**
* 遍历目录计数器
*/
public final static AtomicInteger COUNTER_EACH_FOLDER = new AtomicInteger(0);
public FindFileTask(String targetFolder, String findFileSuffix) {
this.targetFolder = targetFolder;
this.findFileSuffix = findFileSuffix;
}
@Override
protected void compute() {
// 参数校验
if (!FileUtil.exist(targetFolder) || FileUtil.isFile(targetFolder)) {
throw new IllegalArgumentException("targetFolder 必须是存在的 文件夹");
}
File targetFile = FileUtil.file(targetFolder);
File[] files = targetFile.listFiles();
if (ArrayUtil.isEmpty(files)) {
// 空文件夹直接返回
return;
}
// 需要执行 Task List
List<FindFileTask> subFindFileTask = new ArrayList<>(1 << 5);
for (File file : files) {
if (file.isDirectory()) {
// 文件夹
FindFileTask findFileTask = new FindFileTask(file.getAbsolutePath(), findFileSuffix);
subFindFileTask.add(findFileTask);
COUNTER_EACH_FOLDER.incrementAndGet();
} else {
// 文件
String fileSuffix = FileUtil.getSuffix(file);
if (("." + fileSuffix).equalsIgnoreCase(findFileSuffix)) {
// 是目标文件
System.out.println("find: " + file.getName());
COUNTER_TARGET_FILE.incrementAndGet();
}
}
}
if (CollectionUtil.isNotEmpty(subFindFileTask)) {
// 执行所有拆分的 task 如果需要的话
for (FindFileTask findFileTask : invokeAll(subFindFileTask)) {
// 因为是拆分 即使我们不需要获取拆分结果也需要 join 切切
findFileTask.join();
}
}
}
}
```
##### 单元测试
```java
/**
* 测试: Fork/Join RecursiveAction: 目标文件查找
*/
@Test
public void testFindFileByRecursiveAction() {
final String targetFolder = "C:/dev/MavenRepository";
final String findFileSuffix = ".lastUpdated";
FindFileTask findFileTask = new FindFileTask(targetFolder, findFileSuffix);
ForkJoinPool forkJoinPool = new ForkJoinPool();
forkJoinPool.invoke(findFileTask);
System.out.printf("共遍历 : %d 个文件夹%n", FindFileTask.COUNTER_EACH_FOLDER.get());
System.out.printf("找到目标文件 %s : %d 个%n", findFileSuffix, FindFileTask.COUNTER_TARGET_FILE.get());
}
```
##### 运行结果
![](https://img.mercymodest.com/public/20210113150306.png)
## 常用并发工具类
### Worker 模拟: 测试 `CountDownLatch`和`CyclicBarrier`
**Worker.java**
```java
package cn.dankal.share.concurrentutil;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.CyclicBarrier;
import java.util.concurrent.TimeUnit;
/**
* {@link Runnable}
* Worker 简单模拟
* <p>
* 用于测试 {@link CountDownLatch} 和 {@link CyclicBarrier} 的简单使用
* </p>
*
* @author ZGH.MercyModest
* @version V1.0.0
* @create 2021-01-02
*/
public class Worker implements Runnable {
/**
* 工作内容
*/
private final String workContent;
/**
* 工作消耗时间:单位 second
*/
private final long workTimeInMilliSecond;
/**
* <code>CountDownLatch</code>
*/
private CountDownLatch countDownLatch;
/**
* <code>CyclicBarrier</code>
*/
private CyclicBarrier cyclicBarrier;
public Worker(String workContent, long workTimeInMilliSecond, CountDownLatch countDownLatch) {
this.workContent = workContent;
this.workTimeInMilliSecond = workTimeInMilliSecond;
this.countDownLatch = countDownLatch;
}
public Worker(String workContent, long workTimeInMilliSecond, CyclicBarrier cyclicBarrier) {
this.workContent = workContent;
this.workTimeInMilliSecond = workTimeInMilliSecond;
this.cyclicBarrier = cyclicBarrier;
}
@Override
public void run() {
// 工作耗时
try {
TimeUnit.MILLISECONDS.sleep(workTimeInMilliSecond);
System.out.printf("%s 正在: %s 预计耗时 %d 毫秒 %n", Thread.currentThread().getName(), workContent, workTimeInMilliSecond);
} catch (InterruptedException e) {
// ignored
} finally {
if (null != this.countDownLatch) {
// 计数扣减
this.countDownLatch.countDown();
} else if (null != cyclicBarrier) {
try {
// 等待全部执行完成
this.cyclicBarrier.await();
} catch (Exception e) {
// ignored
}
}
}
}
}
```
#### CountDownLatch
##### 介绍
> ​ 闭锁,CountDownLatch 这个类能够使一个线程等待其他线程完成各自的工作后再执行。例如,应用程序的主线程希望在负责启动框架服务的线程已经启动所有的框架服务之后再执行。
> ​ CountDownLatch 是通过一个计数器来实现的,计数器的初始值为初始任务的数量。每当完成了一个任务后,计数器的值就会减1(CountDownLatch.countDown()方法)。当计数器值到达0 时,它表示所有的已经完成了任务,然后在闭锁上等待CountDownLatch.await()方法的线程就可以恢复执行任务。
> ​ 应用场景:
> ​ 实现最大的并行性:有时我们想同时启动多个线程,实现最大程度的并行性。例如,我们想测试一个单例类。如果我们创建一个初始计数为1 的CountDownLatch,并让所有线程都在这个锁上等待,那么我们可以很轻松地完成测试。我们只需调用一次countDown()方法就可以让所有的等待线程同时恢复执行。
> ​ 开始执行前等待n 个线程完成各自任务:例如应用程序启动类要确保在处理
> 用户请求前,所有N 个外部系统已经启动和运行了,例如处理excel 中多个表单。
>
> ![](https://img.mercymodest.com/public/20210113151952.png)
##### 模拟场景小析
> ```text
> 情景小析:
> 我们需要做这样一件事情:
> target: 获取商品详情
> - 获取商品信息 耗时 50 毫秒
> - 获取商品价格 耗时 10 毫秒
> - 商品属性获取并归并(Sku/Spu) 耗时 65 毫秒
> - 假设三个子任务之间没有依赖关系
> ```
##### `CountDownLatchTest.java`
```java
package cn.dankal.share.concurrentutil;
import cn.dankal.share.common.ThreadExecutorFactory;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.ThreadPoolExecutor;
/**
* CountDownLatch 简单使用测试
*
* @author ZGH.MercyModest
* @version V1.0.0
* @create 2021-01-02
*/
public class CountDownLatchTest {
public static void main(String[] args) {
ThreadPoolExecutor threadPoolExecutor = ThreadExecutorFactory.getThreadPoolExecutor("countDownLatch-test");
try {
/*
情景小析:
我们需要做这样一件事情:
target: 获取商品详情
- 获取商品信息 耗时 50 毫秒
- 获取商品价格 耗时 10 毫秒
- 商品属性获取并归并(Sku/Spu) 耗时 65 毫秒
- 假设三个子任务之间没有依赖关系
*/
// target: 获取商品详细
CountDownLatch countDownLatch = new CountDownLatch(3);
final long startTime = System.currentTimeMillis();
// 获取商品信息 50 毫秒
final long getProductDetailElapsed = 50;
Worker getProductDetailWorker = new Worker("获取商品信息", getProductDetailElapsed, countDownLatch);
threadPoolExecutor.execute(getProductDetailWorker);
// 获取商品价格 10 毫秒
final long getPriceElapsed = 10;
Worker getProductPrice = new Worker("获取商品价格", getPriceElapsed, countDownLatch);
threadPoolExecutor.execute(getProductPrice);
//商品属性获取并归并 65毫秒
final long productPropertiesReduceElapsed = 65;
Worker productPropertiesReduceWorker = new Worker("商品属性获取并归并", productPropertiesReduceElapsed, countDownLatch);
threadPoolExecutor.execute(productPropertiesReduceWorker);
try {
System.out.printf("%s 线程即将开始等待 ... ...%n", Thread.currentThread().getName());
countDownLatch.await();
} catch (InterruptedException e) {
// ignored
}
System.out.println("接口返回~");
System.out.println("接口放回理论耗时: " + (getProductDetailElapsed + getPriceElapsed + productPropertiesReduceElapsed) + " 毫秒");
System.out.println("接口放回实际耗时: " + (System.currentTimeMillis() - startTime) + " 毫秒");
} finally {
threadPoolExecutor.shutdown();
}
}
}
```
##### 运行结果
![](https://img.mercymodest.com/public/20210113150908.png)
#### CyclicBarrier
##### 介绍
> ​ CyclicBarrier 的字面意思是可循环使用(Cyclic)的屏障(Barrier)。它要做的事情是,让一组线程到达一个屏障(也可以叫同步点)时被阻塞,直到最后一个线程到达屏障时,屏障才会开门,所有被屏障拦截的线程才会继续运行。
> ​ CyclicBarrier 默认的构造方法是CyclicBarrier(int parties),其参数表示屏障拦截的线程数量,每个线程调用await 方法告诉CyclicBarrier 我已经到达了屏障,然后当前线程被阻塞。
> ​ CyclicBarrier 还提供一个更高级的构造函数CyclicBarrie(r int parties,Runnable
> barrierAction),用于在线程到达屏障时,优先执行barrierAction,方便处理更复杂的业务场景。
> ​ CyclicBarrier 可以用于多线程计算数据,最后合并计算结果的场景。
>
> ![](https://img.mercymodest.com/public/20210113152210.png)
##### 模拟场景小析
> ```text
> 情景小析:
> 我们需要做这样一件事情:
> target: 获取商品详情
> - 获取商品信息 耗时 50 毫秒
> - 获取商品价格 耗时 10 毫秒
> - 商品属性获取并归并(Sku/Spu) 耗时 65 毫秒
> - 假设三个子任务之间没有依赖关系
> ```
##### `CyclicBarrierTest.java`
```java
package cn.dankal.share.concurrentutil;
import cn.dankal.share.common.ThreadExecutorFactory;
import java.util.concurrent.CyclicBarrier;
import java.util.concurrent.ThreadPoolExecutor;
/**
* CyclicBarrier 简单使用测试
*
* @author ZGH.MercyModest
* @version V1.0.0
* @create 2021-01-02
*/
public class CyclicBarrierTest {
public static void main(String[] args) {
ThreadPoolExecutor threadPoolExecutor = ThreadExecutorFactory.getThreadPoolExecutor("countDownLatch-test");
try {
/*
情景小析:
我们需要做这样一件事情:
target: 获取商品详情
- 获取商品信息 耗时 50 毫秒
- 获取商品价格 耗时 10 毫秒
- 商品属性获取并归并(Sku/Spu) 耗时 65 毫秒
- 假设三个子任务之间没有依赖关系
*/
// target: 获取商品详细
CyclicBarrier cyclicBarrier = new CyclicBarrier(4);
final long startTime = System.currentTimeMillis();
// 获取商品信息 50 毫秒
final long getProductDetailElapsed = 50;
Worker getProductDetailWorker = new Worker("获取商品信息", getProductDetailElapsed, cyclicBarrier);
threadPoolExecutor.execute(getProductDetailWorker);
// 获取商品价格 10 毫秒
final long getPriceElapsed = 10;
Worker getProductPrice = new Worker("获取商品价格", getPriceElapsed, cyclicBarrier);
threadPoolExecutor.execute(getProductPrice);
//商品属性获取并归并 65毫秒
final long productPropertiesReduceElapsed = 65;
Worker productPropertiesReduceWorker = new Worker("商品属性获取并归并", productPropertiesReduceElapsed, cyclicBarrier);
threadPoolExecutor.execute(productPropertiesReduceWorker);
try {
System.out.printf("%s 线程即将开始等待 ... ...%n", Thread.currentThread().getName());
cyclicBarrier.await();
} catch (Exception e) {
// ignored
}
System.out.println("接口返回~");
System.out.println("接口放回理论耗时: " + (getProductDetailElapsed + getPriceElapsed + productPropertiesReduceElapsed) + " 毫秒");
System.out.println("接口放回实际耗时: " + (System.currentTimeMillis() - startTime) + " 毫秒");
} finally {
threadPoolExecutor.shutdown();
}
}
}
```
##### 运行结果
![](https://img.mercymodest.com/public/20210113151050.png)
### CountDownLatch和CyclicBarrier
> ​ CountDownLatch 的计数器只能使用一次,而CyclicBarrier 的计数器可以反复
> 使用。
> ​ CountDownLatch.await 一般阻塞工作线程,所有的进行预备工作的线程执行
> countDown,而CyclicBarrier 通过工作线程调用await 从而自行阻塞,直到所有工
> 作线程达到指定屏障,再大家一起往下走。
> ​ 在控制多个线程同时运行上,CountDownLatch 可以不限线程数量,而CyclicBarrier 是固定线程数。同时,CyclicBarrier 还可以提供一个barrierAction,合并多线程计算结果。
### Semaphore
#### 介绍
> ​ Semaphore(信号量)是用来控制同时访问特定资源的线程数量,它通过协调各个线程,以保证合理的使用公共资源。应用场景Semaphore 可以用于做流量控制,特别是公用资源有限的应用场景,比如数据库连接。假如有一个需求,要读取几万个文件的数据,因为都是IO 密集型任务,我们可以启动几十个线程并发读取,但是如果读到内存后,还需要存储到数据库中,而数据库的连接数只有10 个,这时我们必须控制只有10个线程同时获取数据库连接保存数据,否则会报错无法获取数据库连接。这个时候,就可以使用Semaphore 来做流量控制。Semaphore 的构造方法Semaphore(int permits)接受一个整型的数字,表示可用的许可证数量。Semaphore 的用法也很简单,首先线程使用Semaphore的acquire()方法获取一个许可证,使用完之后调用release()方法归还许可证。还可以用tryAcquire()方法尝试获取许可证。
>
> Semaphore 还提供一些其他方法,简略介绍如下。
>
> - intavailablePermits():返回此信号量中当前可用的许可证数。
> - intgetQueueLength():返回正在等待获取许可证的线程数。
> - booleanhasQueuedThreads():是否有线程正在等待获取许可证。
> - void reducePermits(int reduction):减少reduction 个许可证,是个protected方法。
> - Collection getQueuedThreads():返回所有等待获取许可证的线程集合,是个protected 方法。
>
> ![](https://img.mercymodest.com/public/20210113152835.png)
#### 模拟场景小析
**使用 Semaphore 简单实现令牌桶限流**
#### `TokenBucket.java`
```java
package cn.dankal.share.concurrentutil;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.Semaphore;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicInteger;
/**
* token Bucket 简单模拟
* <p>
* 基于 {@link Semaphore} 简单实现
* </p>
*
* @author ZGH.MercyModest
* @version V1.0.0
* @create 2021-01-02
*/
public class TokenBucket {
/**
* <code>Semaphore</code>
*/
private final Semaphore semaphore;
/**
* <code>CountDownLatch</code>
*/
private final CountDownLatch countDownLatch;
/**
* 令牌统计
*/
private final AtomicInteger tokenCounter = new AtomicInteger(0);
/**
* 成功线程数统计
*/
private final AtomicInteger successCounter = new AtomicInteger(0);
/**
* 失败线程数统计
*/
private final AtomicInteger fairCounter = new AtomicInteger(0);
public TokenBucket(Semaphore semaphore, CountDownLatch countDownLatch1) {
this.semaphore = semaphore;
this.countDownLatch = countDownLatch1;
}
/**
* 获取令牌
*/
public void getToken() {
try {
final long getTokenTimeout = 5;
if (semaphore.tryAcquire(getTokenTimeout, TimeUnit.MILLISECONDS)) {
// 成功获取到信号量: 获取令牌成功
// 保证 tryAcquire 超时
int tokenNum = tokenCounter.incrementAndGet();
TimeUnit.SECONDS.sleep(1);
System.out.printf("%s 成功获取到 %d 号令牌 %n", Thread.currentThread().getName(), tokenNum);
successCounter.incrementAndGet();
} else {
// 获取令牌失败
System.out.printf("%s 未获取到令牌 %n", Thread.currentThread().getName());
fairCounter.incrementAndGet();
}
} catch (InterruptedException e) {
// ignored
} finally {
// 成功/失败统计计数扣减
this.countDownLatch.countDown();
}
}
/**
* 成功获取令桶统计
*
* @return 成功获取令牌统计
*/
public int successCount() {
return successCounter.get();
}
/**
* 获取令牌失败统计
*
* @return 未成功获取令牌统计
*/
public int fairCount() {
return fairCounter.get();
}
}
```
#### `SemaphoreTest.java`
```java
package cn.dankal.share.concurrentutil;
import cn.dankal.share.common.ThreadExecutorFactory;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.Semaphore;
import java.util.concurrent.ThreadPoolExecutor;
/**
* Semaphore 简单使用测试
*
* @author ZGH.MercyModest
* @version V1.0.0
* @create 2021-01-02
*/
public class SemaphoreTest {
public static void main(String[] args) {
ThreadPoolExecutor threadPoolExecutor = ThreadExecutorFactory.getThreadPoolExecutor("semaphore-test");
// semaphore: 令牌桶只有 3 个令牌
Semaphore semaphore = new Semaphore(3);
// 参与抢令牌的线程数
final int count = 10;
// 确保统计顺序执行完(成功/失败统计)
CountDownLatch successAndFairCountDownLatch = new CountDownLatch(count);
TokenBucket tokenBucket = new TokenBucket(semaphore, successAndFairCountDownLatch);
try {
for (int i = 0; i < count; i++) {
threadPoolExecutor.execute(tokenBucket::getToken);
}
successAndFairCountDownLatch.await();
System.out.println("参与线程数: " + count);
System.out.println("成功线程数:" + tokenBucket.successCount());
System.out.println("失败线程数:" + tokenBucket.fairCount());
} catch (InterruptedException e) {
// ignored
} finally {
threadPoolExecutor.shutdown();
}
}
}
```
#### 运行结果
![](https://img.mercymodest.com/public/20210113151337.png)
### Exchanger
#### 介绍
> ​ Exchanger(交换者)是一个用于线程间协作的工具类。Exchanger 用于进行线程间的数据交换。它提供一个同步点,在这个同步点,两个线程可以交换彼此的数据。这两个线程通过exchange 方法交换数据,如果第一个线程先执行exchange()方法,它会一直等待第二个线程也执行exchange 方法,当两个线程都到达同步点时,这两个线程就可以交换数据,将本线程生产出来的数据传递给对方。
>
> ![](https://img.mercymodest.com/public/20210113153358.png)
>
> 官方注释:
>
> ##### 原版:
>
> ```text
> A synchronization point at which threads can pair and swap elements within pairs. Each thread presents some object on entry to the exchange method, matches with a partner thread, and receives its partner's object on return. An Exchanger may be viewed as a bidirectional form of a SynchronousQueue. Exchangers may be useful in applications such as genetic algorithms and pipeline designs.
>
> Sample Usage:
> Here are the highlights of a class that uses an Exchanger to swap buffers between threads so that the thread filling the buffer gets a freshly emptied one when it needs it, handing off the filled one to the thread emptying the buffer.
> ```
>
> ##### 大意可能是:
>
> ```text
> 一个同步点,在这个点上,线程可以配对和交换配对内的元素。每个线程在进入交换方法时都会呈现一些对象,与伙伴线程进行匹配,并在返回时接收伙伴的对象。Exchanger可以看作是SynchronousQueue的一种双向形式。交换器在遗传算法和流水线设计等应用中可能很有用。
> 示例用法。
> 下面是一个使用Exchanger在线程之间交换缓冲区的类的亮点,这样当需要时,填充缓冲区的线程就会得到一个新的空的缓冲区,把填充的缓冲区交给清空缓冲区的线程。
> ```
>
> ##### 官方示例
>
> ![](https://img.mercymodest.com/public/20210113153732.png)
#### `ExchangerTest.java`
```java
package cn.dankal.share.concurrentutil;
import cn.dankal.share.common.ThreadExecutorFactory;
import java.util.HashMap;
import java.util.UUID;
import java.util.concurrent.Exchanger;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
/**
* {@link Exchanger} 的简单使用
*
* @author ZGH.MercyModest
* @version V1.0.0
* @create 2021-01-02
*/
public class ExchangerTest {
public static void main(String[] args) {
/*
应用场景:
- 在两个线程之间进行数据交换
- 流水线式数据处理
- 线程一 处理完数据之后,将数据交给 线程二继续处理
*/
ThreadPoolExecutor threadPoolExecutor = ThreadExecutorFactory.getThreadPoolExecutor("exchanger-test");
Exchanger<HashMap<String, Object>> mapExchanger = new Exchanger<>();
try {
// 线程一 先处理
threadPoolExecutor.execute(() -> {
final HashMap<String, Object> dataMap = new HashMap<>(1 << 5);
dataMap.put(Thread.currentThread().getName(), UUID.randomUUID().toString().replace("-", ""));
System.out.println(Thread.currentThread().getName() + " 开始处理数据");
try {
TimeUnit.SECONDS.sleep(1);
System.out.printf("%s 线程 原始数据: %s%n", Thread.currentThread().getName(), dataMap);
System.out.println(Thread.currentThread().getName() + " 完成数据处理,即将进行数据传递");
HashMap<String, Object> exchangeResult = mapExchanger.exchange(dataMap);
System.out.printf("%s 获取交换后的结果 %s%n", Thread.currentThread().getName(), exchangeResult);
} catch (InterruptedException e) {
// ignored
}
});
// 线程二 继续处理
threadPoolExecutor.execute(() -> {
System.out.println(Thread.currentThread().getName() + " 开始处理数据");
try {
HashMap<String, Object> emptyMap = new HashMap<>(1);
TimeUnit.SECONDS.sleep(1);
System.out.printf("%s 线程 原始数据: %s%n", Thread.currentThread().getName(), emptyMap);
System.out.println(Thread.currentThread().getName() + " 完成数据处理,即将进行数据传递");
HashMap<String, Object> exchangeResult = mapExchanger.exchange(emptyMap);
System.out.printf("%s 获取交换后的结果 %s%n", Thread.currentThread().getName(), exchangeResult);
} catch (InterruptedException e) {
// ignored
}
});
} finally {
threadPoolExecutor.shutdown();
}
}
}
```
## 组合式异步编程: `CompletableFuture`
![](https://img.mercymodest.com/public/20210113162009.png)
### `Future`的不足
> ​ Future 是Java 5 添加的类,用来描述一个异步计算的结果。你可以使用isDone方法检查计算是否完成,或者使用get 阻塞住调用线程,直到计算完成返回结果,你也可以使用cancel 方法停止任务的执行。虽然Future 以及相关使用方法提供了异步执行任务的能力,但是对于结果的获取却是很不方便,只能通过阻塞或者轮询的方式得到任务的结果。阻塞的方式显然和我们的异步编程的初衷相违背,轮询的方式又会耗费无谓的CPU 资源,而且也不能及时地得到计算结果,为什么不能用观察者设计模式当计算结果完成及时通知监听者呢?。
> ​ Java 的一些框架,比如Netty,自己扩展了Java 的Future 接口,提供了addListener 等多个扩展方法,Google guava 也提供了通用的扩展Future:ListenableFuture、SettableFuture 以及辅助类Futures 等,方便异步编程。
> ​ 同时Future 接口很难直接表述多个Future 结果之间的依赖性。实际开发中,我们经常需要达成以下目的:
> ​ 将两个异步计算合并为一个——这两个异步计算之间相互独立,同时第二个
> 又依赖于第一个的结果。
> ​ 等待Future 集合中的所有任务都完成。
> ​ 仅等待Future 集合中最快结束的任务完成(有可能因为它们试图通过不同
> 的方式计算同一个值),并返回它的结果。
> ​ 应对Future 的完成事件(即当Future 的完成事件发生时会收到通知,并
> 能使用Future 计算的结果进行下一步的操作,不只是简单地阻塞等待操作的结
> 果)
### `CompletableFuture`
> ​ JDK1.8 才新加入的一个实现类CompletableFuture,实现了Future<T>,CompletionStage<T>两个接口。实现了Future 接口,意味着可以像以前一样通过阻塞或者轮询的方式获得结果
### CompletableFuture 的创建
**除了直接new 出一个CompletableFuture 的实例,还可以通过工厂方法创建CompletableFuture 的实例**
#### 工厂方法
![](https://img.mercymodest.com/public/20210113162455.png)
> ​ Asynsc 表示异步,而supplyAsync 与runAsync 不同在与前者异步返回一个结果,后者是void.第二个函数第二个参数表示是用我们自己创建的线程池,否则采用默认的ForkJoinPool.commonPool()作为它的线程池。
### 获取CompletableFuture结果集
> - public T get()
>
> - public T get(long timeout, TimeUnit unit)
>
> - public T getNow(T valueIfAbsent)
>
> - public T join()
>
> ​ getNow 有点特殊,如果结果已经计算完则返回结果或者抛出异常,否则返回给定的valueIfAbsent 值。
> ​ join 返回计算的结果或者抛出一个unchecked 异常(CompletionException),它和get 对抛出的异常的处理有些细微的区别。
#### `get()`
![](https://img.mercymodest.com/public/20210113164218.png)
##### 单元测试
```java
/**
* completableFuture 结果获取: get()
*/
@Test
public void testCompletableFutureGet() {
ThreadPoolExecutor threadPoolExecutor = ThreadExecutorFactory.getThreadPoolExecutor("completableFuture-result-get-test");
try {
// 使用 get() 获取 completableFuture 运行结果
CompletableFuture<Integer> completableFuture = CompletableFuture.supplyAsync(() -> {
int result = RandomUtil.randomInt(1, 100);
System.out.printf("线程 %s 已生产随机数 %d%n", Thread.currentThread().getName(), result);
return result;
}, threadPoolExecutor);
try {
System.out.printf("%s 线程 即将开始获取 completableFuture 结果%n", Thread.currentThread().getName());
Integer result = completableFuture.get();
System.out.println(result);
} catch (Exception exception) {
exception.printStackTrace();
}
} finally {
threadPoolExecutor.shutdown();
}
}
```
##### 运行结果
![](https://img.mercymodest.com/public/20210113164057.png)
#### `get(long timeout, TimeUnit unit)`
![](https://img.mercymodest.com/public/20210113164238.png)
##### 单元测试
```java
/**
* completableFuture 结果获取: get(long timeout, TimeUnit unit)
*/
@Test
public void testCompletableFutureGetTimeout() {
// 使用 get() 获取 completableFuture 运行结果
CompletableFuture<Integer> completableFuture = CompletableFuture.supplyAsync(() -> {
int result = RandomUtil.randomInt(1, 100);
System.out.printf("线程 %s 已生产随机数 %d%n", Thread.currentThread().getName(), result);
try {
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
// ignored
}
return result;
}, THREAD_POOL_EXECUTOR);
try {
System.out.printf("%s 线程 即将开始获取 completableFuture 结果%n", Thread.currentThread().getName());
Integer result = completableFuture.get(100, TimeUnit.MILLISECONDS);
System.out.println(result);
} catch (Exception exception) {
System.out.println(exception.getMessage());
}
}
```
##### 运行结果
![](https://img.mercymodest.com/public/20210113164921.png)
#### `getNow(T valueIfAbsent)`
![](https://img.mercymodest.com/public/20210113165130.png)
##### 单元测试
```java
/**
* completableFuture 结果获取: getNow(T valueIfAbsent)
*/
@Test
public void testCompletableFutureGetNow() {
// 使用 get() 获取 completableFuture 运行结果
CompletableFuture<Integer> completableFuture = CompletableFuture.supplyAsync(() -> {
int result = RandomUtil.randomInt(1, 100);
System.out.printf("线程 %s 已生产随机数 %d%n", Thread.currentThread().getName(), result);
try {
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
// ignored
}
return result;
}, THREAD_POOL_EXECUTOR);
try {
System.out.printf("%s 线程 即将开始获取 completableFuture 结果%n", Thread.currentThread().getName());
Integer result = completableFuture.getNow(-999);
System.out.println(result);
} catch (Exception exception) {
System.out.println(exception.getMessage());
}
}
```
##### 运行结果
![](https://img.mercymodest.com/public/20210113165259.png)
#### `join()`
![](https://img.mercymodest.com/public/20210113165414.png)
##### 单元测试
```java
/**
* completableFuture 结果获取: join()
*/
@Test
public void testCompletableFutureJoin() {
// 使用 get() 获取 completableFuture 运行结果
CompletableFuture<Integer> completableFuture = CompletableFuture.supplyAsync(() -> {
int result = RandomUtil.randomInt(1, 100);
System.out.printf("线程 %s 已生产随机数 %d%n", Thread.currentThread().getName(), result);
try {
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
// ignored
}
return result;
}, THREAD_POOL_EXECUTOR);
try {
System.out.printf("%s 线程 即将开始获取 completableFuture 结果%n", Thread.currentThread().getName());
Integer result = completableFuture.join();
System.out.println(result);
} catch (Exception exception) {
System.out.println(exception.getMessage());
}
}
```
##### 运行结果
![](https://img.mercymodest.com/public/20210113165558.png)
### CompletableFuture辅助方法
#### `allOf(CompletableFuture<?>... cfs)`
![](https://img.mercymodest.com/public/20210113165913.png)
##### 单元测试
```java
/**
* 测试 CompletableFuture:allOf(CompletableFuture<?>... cfs)
*/
@Test
public void testAllOf() {
Runnable runnable = () -> {
try {
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
// ignored
}
System.out.printf("线程 %s 完成工作%n", Thread.currentThread().getName());
};
CompletableFuture<Void> completableFuture01 = CompletableFuture.runAsync(runnable);
CompletableFuture<Void> completableFuture02 = CompletableFuture.runAsync(runnable);
CompletableFuture<Void> completableFuture03 = CompletableFuture.runAsync(runnable);
CompletableFuture<Void> completableFuture04 = CompletableFuture.runAsync(runnable);
CompletableFuture<Void> completableFuture05 = CompletableFuture.runAsync(runnable);
System.out.println("all completableFuture is starting the work or will start work");
CompletableFuture.allOf(completableFuture01, completableFuture02, completableFuture03, completableFuture04, completableFuture05)
.join();
System.out.println("completableFuture all finished the work");
}
```
##### 运行结果
![](https://img.mercymodest.com/public/20210113171254.png)
#### `anyOf(CompletableFuture<?>... cfs)`
![](https://img.mercymodest.com/public/20210113171422.png)
##### 单元测试
```java
/**
* 测试 CompletableFuture:anyOf(CompletableFuture<?>... cfs)
*/
@Test
public void testAnyOf() {
Runnable runnable = () -> {
try {
TimeUnit.SECONDS.sleep(RandomUtil.randomInt(1,10));
} catch (InterruptedException e) {
// ignored
}
System.out.printf("线程 %s 完成工作%n", Thread.currentThread().getName());
};
CompletableFuture<Void> completableFuture01 = CompletableFuture.runAsync(runnable);
CompletableFuture<Void> completableFuture02 = CompletableFuture.runAsync(runnable);
CompletableFuture<Void> completableFuture03 = CompletableFuture.runAsync(runnable);
CompletableFuture<Void> completableFuture04 = CompletableFuture.runAsync(runnable);
CompletableFuture<Void> completableFuture05 = CompletableFuture.runAsync(runnable);
System.out.println("all completableFuture is starting the work or will start work");
CompletableFuture.anyOf(completableFuture01, completableFuture02, completableFuture03, completableFuture04, completableFuture05)
.join();
System.out.println("completableFuture all finished the work");
}
```
##### 运行结果
![](https://img.mercymodest.com/public/20210113171657.png)
### CompletableFuture 方法归类
> ​ CompletionStage 是一个接口,从命名上看得知是一个完成的阶段,它代表了一个特定的计算的阶段,可以同步或者异步的被完成。你可以把它看成一个计算流水线上的一个单元,并最终会产生一个最终结果,这意味着几个CompletionStage 可以串联起来,一个完成的阶段可以触发下一阶段的执行,接着触发下一次,再接着触发下一次。
> ​ 总结CompletableFuture 几个关键点:
> ​ 1、计算可以由Future ,Consumer 或者Runnable 接口中的apply,accept或者run 等方法表示。
> ​ 2、计算的执行主要有以下
> ​ a. 默认执行
> ​ b. 使用默认的CompletionStage 的异步执行提供者异步执行。这些方法名使用someActionAsync 这种格式表示。
> ​ c. 使用Executor 提供者异步执行。这些方法同样也是someActionAsync 这种格式,但是会增加一 个Executor 参数。
##### `thenApply`
**上一个CompletFuture执行完之后执行,使用上一个CompletableFutre的结果继续执行相应操作并返回最终结果**
**生产类型**
![](https://img.mercymodest.com/public/20210113172738.png)
###### `thenApply(Function<? super T,? extends U> fn)`
**与上一个CompletableFuture共用同一个线程**
**单元测试**
```java
/**
* 测试 CompletableFuture: thenApply(Function<? super T,? extends U> fn)
*/
@Test
public void testThenApply() throws ExecutionException, InterruptedException {
final long startMs = System.currentTimeMillis();
CompletableFuture<Integer> completableFuture = CompletableFuture
.supplyAsync(() -> {
try {
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
// ignored
}
int randomInt = RandomUtil.randomInt(1, 100);
System.out.printf("%s 成功生产了 %d%n", Thread.currentThread().getName(), randomInt);
return randomInt;
},THREAD_POOL_EXECUTOR)
.thenApply(result -> {
System.out.printf("%s 成功获取结果,耗时 %d 秒%n", Thread.currentThread().getName(), (System.currentTimeMillis() - startMs) / 1000);
System.out.printf("%s 线程即将消费 %d%n", Thread.currentThread().getName(), result);
return result + 100;
});
Integer result = completableFuture.get();
System.out.println("result = " + result);
}
```
**运行结果**
![](https://img.mercymodest.com/public/20210113175407.png)
###### `thenApplyAsync(Function<? super T,? extends U> fn, Executor executor)`
**在上一个CompletableFutre执行完后之后,开启一个新的线程去执行**
**单元测试**
```java
/**
* 测试 CompletableFuture: thenApplyAsync(Function<? super T,? extends U> fn, Executor executor)
*/
@Test
public void testThenApplyAsync() throws ExecutionException, InterruptedException {
final long startMs = System.currentTimeMillis();
CompletableFuture<Integer> completableFuture = CompletableFuture
.supplyAsync(() -> {
try {
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
// ignored
}
int randomInt = RandomUtil.randomInt(1, 100);
System.out.printf("%s 成功生产了 %d%n", Thread.currentThread().getName(), randomInt);
return randomInt;
},THREAD_POOL_EXECUTOR)
.thenApplyAsync(result -> {
System.out.printf("%s 成功获取结果,耗时 %d 秒%n", Thread.currentThread().getName(), (System.currentTimeMillis() - startMs) / 1000);
System.out.printf("%s 线程即将消费 %d%n", Thread.currentThread().getName(), result);
return result + 100;
}, THREAD_POOL_EXECUTOR);
Integer result = completableFuture.get();
System.out.println("result = " + result);
}
```
**运行结果**
![](https://img.mercymodest.com/public/20210113175305.png)
##### `thenAccpet`
**上一个CompletFuture执行完之后执行,使用上一个CompletableFutre的结果继续执行相应操作无返回值**
**消费类型**
![](https://img.mercymodest.com/public/20210113180822.png)
##### `thenRun`
**对上一步的计算结果不关心,执行下一个操作,入参是一个Runnable 的实
例,表示上一步完成后执行的操作**
![](https://img.mercymodest.com/public/20210113181723.png)
##### `thenCombine`
![](https://img.mercymodest.com/public/20210113181949.png)
- **需要上一步的处理返回值,并且other 代表的CompletionStage 有返回值之
后,利用这两个返回值,进行转换后返回指定类型的值**
- **两个CompletionStage 是并行执行的,它们之间并没有先后依赖顺序(==并行执行==),other
并不会等待先前的CompletableFuture 执行完毕后再执行**
- **combine:使联合,联合,结合**
###### 用例测试
**测试方法**
```java
thenCombineAsync(
CompletionStage<? extends U> other,
BiFunction<? super T,? super U,? extends V> fn,
Executor executor
)
```
**单元测试**
```java
/**
* 测试 CompletableFuture:
* <pre>
* {@code
* thenCombineAsync(
* CompletionStage<? extends U> other,
* BiFunction<? super T,? super U,? extends V> fn, Executor executor)
* }
* </pre>
*/
@Test
public void testThenCombineAsync() throws ExecutionException, InterruptedException {
CompletableFuture<Integer> integerCompletableFuture = CompletableFuture.supplyAsync(() -> {
int randomInt = RandomUtil.randomInt(1, 100);
final long sleepTime = 5;
try {
TimeUnit.SECONDS.sleep(sleepTime);
} catch (InterruptedException e) {
//ignored
}
System.out.printf("%s 生产了 %d 耗时 %d 秒 %n", Thread.currentThread().getName(), randomInt, sleepTime);
return randomInt;
}, THREAD_POOL_EXECUTOR);
final long startMs = System.currentTimeMillis();
Integer result = CompletableFuture.supplyAsync(() -> {
int randomInt = RandomUtil.randomInt(1, 10);
final long sleepTime = 2;
try {
TimeUnit.SECONDS.sleep(sleepTime);
} catch (InterruptedException e) {
//ignored
}
System.out.printf("%s 生产了 %d 耗时 %d 秒%n", Thread.currentThread().getName(), randomInt, sleepTime);
return randomInt;
}, THREAD_POOL_EXECUTOR)
.thenCombineAsync(integerCompletableFuture, Integer::sum)
.get();
System.out.printf("获取最终结果耗时 %d 秒%n", (System.currentTimeMillis() - startMs) / 1000);
System.out.println("result = " + result);
}
```
**运行结果**
![](https://img.mercymodest.com/public/20210114100039.png)
##### `thenCompose`
![](https://img.mercymodest.com/public/20210113183118.png)
- **对于Compose 可以连接两个CompletableFuture,其内部处理逻辑是当第一个CompletableFuture 处理没有完成时会合并成一个CompletableFuture,如果处理完成,第二个future 会紧接上一个CompletableFuture 进行处理**
- **第一个CompletableFuture 的处理结果是第二个future 需要的输入参数**
- **compose: 构成,组成**
- **第一个CompletableFuture和第二个CompletableFuture是==串行执行==**
###### 用例测试
单元测试**
```java
/**
* 测试 CompletableFuture:
* <pre>
* {@code
* thenComposeAsync(
* Function<? super T, ? extends CompletionStage<U>> fn,
* Executor executor)
* }
* </pre>
*/
@Test
public void testThenComposeAsync() throws ExecutionException, InterruptedException {
final long startMs = System.currentTimeMillis();
Integer result = CompletableFuture.supplyAsync(() -> {
int randomInt = RandomUtil.randomInt(1, 10);
final long sleepTime = 5;
try {
TimeUnit.SECONDS.sleep(sleepTime);
} catch (InterruptedException e) {
//ignored
}
System.out.printf("%s 生产了 %d 耗时 %d 秒%n", Thread.currentThread().getName(), randomInt, sleepTime);
return randomInt;
}, THREAD_POOL_EXECUTOR)
.thenComposeAsync(integer ->
CompletableFuture.supplyAsync(() -> {
System.out.printf("%s 获取到值 %d%n", Thread.currentThread().getName(), integer);
int randomInt = RandomUtil.randomInt(1, 100);
final long sleepTime = 2;
try {
TimeUnit.SECONDS.sleep(sleepTime);
} catch (InterruptedException e) {
//ignored
}
System.out.printf("%s 生产了 %d 耗时 %d 秒%n", Thread.currentThread().getName(), randomInt, sleepTime);
return randomInt + integer;
}, THREAD_POOL_EXECUTOR))
.get();
System.out.printf("获取最终结果耗时 %d 秒%n", (System.currentTimeMillis() - startMs) / 1000);
System.out.println("result = " + result);
}
```
**运行结果**
![](https://img.mercymodest.com/public/20210114100347.png)
##### `thenAccpetBoth`
![](https://img.mercymodest.com/public/20210114100827.png)
**需要上一步的处理返回值,并且other 代表的CompletionStage 有返回值之
后,利用这两个返回值,进行消费**
**单元测试**
```java
/**
* 测试 CompletableFuture:
* <pre>
* {@code
* thenAcceptBothAsync(
* CompletionStage<? extends U> other,
* BiConsumer<? super T, ? super U> action, Executor executor)
* }
* </pre>
*/
@Test
public void testThenAcceptBothAsync() throws ExecutionException, InterruptedException {
final long startMs = System.currentTimeMillis();
CompletableFuture.supplyAsync(() -> {
int randomInt = RandomUtil.randomInt(1, 10);
final long sleepTime = 5;
try {
TimeUnit.SECONDS.sleep(sleepTime);
} catch (InterruptedException e) {
//ignored
}
System.out.printf("%s 生产了 %d 耗时 %d 秒%n", Thread.currentThread().getName(), randomInt, sleepTime);
return randomInt;
}, THREAD_POOL_EXECUTOR)
.thenAcceptBothAsync(
CompletableFuture.supplyAsync(() -> {
int randomInt = RandomUtil.randomInt(1, 100);
final long sleepTime = 2;
try {
TimeUnit.SECONDS.sleep(sleepTime);
} catch (InterruptedException e) {
//ignored
}
System.out.printf("%s 生产了 %d 耗时 %d 秒%n", Thread.currentThread().getName(), randomInt, sleepTime);
return randomInt;
}), (result1, result2) -> System.out.printf("两个 CompletableFuture 和: %d%n", result1 + result2), THREAD_POOL_EXECUTOR)
.get();
System.out.printf("获取最终结果耗时 %d 秒%n", (System.currentTimeMillis() - startMs) / 1000);
}
```
**运行结果**
![](https://img.mercymodest.com/public/20210122154844.png)
##### `runAfterBoth`
![](https://img.mercymodest.com/public/20210114103153.png)
**不关心这两个CompletionStage 的结果,只关心这两个CompletionStage 都执
行完毕,之后再进行操作(Runnable)**
**单元测试**
```java
/**
* 测试 CompletableFuture:
* <pre>
* {@code
* runAfterBothAsync(
* CompletionStage<?> other,
* Runnable action,
* Executor executor
* )
* }
* </pre>
*/
@Test
public void testRunAfterBothAsync() throws ExecutionException, InterruptedException {
final long startMs = System.currentTimeMillis();
CompletableFuture.supplyAsync(() -> {
int randomInt = RandomUtil.randomInt(1, 10);
final long sleepTime = 5;
try {
TimeUnit.SECONDS.sleep(sleepTime);
} catch (InterruptedException e) {
//ignored
}
System.out.printf("%s 生产了 %d 耗时 %d 秒%n", Thread.currentThread().getName(), randomInt, sleepTime);
return randomInt;
}, THREAD_POOL_EXECUTOR)
.runAfterBothAsync(
CompletableFuture.supplyAsync(() -> {
int randomInt = RandomUtil.randomInt(1, 100);
final long sleepTime = 2;
try {
TimeUnit.SECONDS.sleep(sleepTime);
} catch (InterruptedException e) {
//ignored
}
System.out.printf("%s 生产了 %d 耗时 %d 秒%n", Thread.currentThread().getName(), randomInt, sleepTime);
return randomInt;
}, THREAD_POOL_EXECUTOR), () -> System.out.printf("%s :两个 CompletableFuture运行耗时 %d 秒%n", Thread.currentThread().getName(), (System.currentTimeMillis() - startMs) / 1000)
, THREAD_POOL_EXECUTOR)
.get();
}
```
**运行结果**
![](https://img.mercymodest.com/public/20210122154937.png)
##### `applyToEither`
![](https://img.mercymodest.com/public/20210114103650.png)
**两个CompletionStage,谁计算的快,我就用那个CompletionStage 的结果进
行下一步的转化操作。现实开发场景中,总会碰到有两种渠道完成同一个事情,
所以就可以调用这个方法,找一个最快的结果进行处理。**
**单元测试**
```java
/**
* 测试 CompletableFuture:
* <pre>
* {@code
* applyToEitherAsync(
* CompletionStage<? extends T> other, Function<? super T, U> fn,
* Executor executor)
* }
* </pre>
*/
@Test
public void testApplyToEitherAsync() throws ExecutionException, InterruptedException {
final long startMs = System.currentTimeMillis();
Integer result = CompletableFuture.supplyAsync(() -> {
int randomInt = RandomUtil.randomInt(1, 10);
final long sleepTime = 6;
try {
TimeUnit.SECONDS.sleep(sleepTime);
} catch (InterruptedException e) {
//ignored
}
System.out.printf("%s 生产了 %d 耗时 %d 秒%n", Thread.currentThread().getName(), randomInt, sleepTime);
return randomInt;
}, THREAD_POOL_EXECUTOR)
.applyToEitherAsync(
CompletableFuture.supplyAsync(() -> {
int randomInt = RandomUtil.randomInt(1, 100);
final long sleepTime = 2;
try {
TimeUnit.SECONDS.sleep(sleepTime);
} catch (InterruptedException e) {
//ignored
}
System.out.printf("%s 生产了 %d 耗时 %d 秒%n", Thread.currentThread().getName(), randomInt, sleepTime);
return randomInt;
}, THREAD_POOL_EXECUTOR), (i) -> {
System.out.printf("%s :获取到结果: %d 等待耗时 %d 秒%n", Thread.currentThread().getName(), i, (System.currentTimeMillis() - startMs) / 1000);
final long sleepTime = 3;
try {
TimeUnit.SECONDS.sleep(sleepTime);
} catch (InterruptedException e) {
// ignored
}
System.out.printf("%s 处理结果耗时 %d 秒%n", Thread.currentThread().getName(), sleepTime);
return i + 100;
}
, THREAD_POOL_EXECUTOR)
.get();
System.out.printf("获取到最终结果: %d, 耗时 %d 秒 %n", result, (System.currentTimeMillis() - startMs) / 1000);
}
```
**运行结果**
![](https://img.mercymodest.com/public/20210122155103.png)
##### `acceptEither`
![](https://img.mercymodest.com/public/20210114104214.png)
**两个CompletionStage,谁计算的快,我就用那个CompletionStage 的结果进
行下一步的消费操作。**
**单元测试**
```java
/**
* 测试 CompletableFuture:
* <pre>
* {@code
* acceptEitherAsync(
* CompletionStage<? extends T> other, Consumer<? super T> action,
* Executor executor)
* }
* </pre>
*/
@Test
public void testAcceptEitherAsync() throws ExecutionException, InterruptedException {
final long startMs = System.currentTimeMillis();
CompletableFuture.supplyAsync(() -> {
int randomInt = RandomUtil.randomInt(1, 10);
final long sleepTime = 5;
try {
TimeUnit.SECONDS.sleep(sleepTime);
} catch (InterruptedException e) {
//ignored
}
System.out.printf("%s 生产了 %d 耗时 %d 秒%n", Thread.currentThread().getName(), randomInt, sleepTime);
return randomInt;
}, THREAD_POOL_EXECUTOR)
.acceptEitherAsync(
CompletableFuture.supplyAsync(() -> {
int randomInt = RandomUtil.randomInt(1, 100);
final long sleepTime = 2;
try {
TimeUnit.SECONDS.sleep(sleepTime);
} catch (InterruptedException e) {
//ignored
}
System.out.printf("%s 生产了 %d 耗时 %d 秒%n", Thread.currentThread().getName(), randomInt, sleepTime);
return randomInt;
}, THREAD_POOL_EXECUTOR), (result) -> System.out.printf("%s :获取到结果: %d 等待耗时 %d 秒%n", Thread.currentThread().getName(), result, (System.currentTimeMillis() - startMs) / 1000)
, THREAD_POOL_EXECUTOR)
.get();
}
```
**运行结果**
![](https://img.mercymodest.com/public/20210122155146.png)
##### `runAfterEither`
![](https://img.mercymodest.com/public/20210114113818.png)
**两个CompletionStage,任何一个完成了都会执行下一步的操作(Runnable)**
`exceptionally`
![](https://img.mercymodest.com/public/20210114114001.png)
**当运行时出现了异常,可以通过exceptionally 进行补偿。**
##### `whenComplete`
![](https://img.mercymodest.com/public/20210114114644.png)
**action 执行完毕后它的结果返回原始的CompletableFuture 的计算结果或者返回
异常。所以不会对结果产生任何的作用。(如果主CompletableFuture发生异常会中断程序执行)**
**单元测试**
```java
/**
* 测试 CompletableFuture:
* <pre>
* {@code
* whenCompleteAsync(
* BiConsumer<? super T, ? super Throwable> action, Executor executor)
* }
* </pre>
*/
@Test
public void testWhenCompeteAsync() throws ExecutionException, InterruptedException {
String result = CompletableFuture.supplyAsync(() -> "dankal", THREAD_POOL_EXECUTOR)
.whenCompleteAsync((str, exception) -> {
if (StrUtil.isNotBlank(str)) {
System.out.println("响应结果: " + str);
}
if (null != exception) {
System.out.println("发生异常: " + exception.getMessage());
}
}, THREAD_POOL_EXECUTOR)
.get();
System.out.println("result = " + result);
result = CompletableFuture.supplyAsync(() -> {
if (true) {
throw new RuntimeException("just throw a exception");
}
return "dankal";
}, THREAD_POOL_EXECUTOR)
.whenCompleteAsync((str, exception) -> {
if (StrUtil.isNotBlank(str)) {
System.out.println("响应结果: " + str);
}
if (null != exception) {
System.out.println("发生异常: " + exception.getMessage());
}
}, THREAD_POOL_EXECUTOR)
.get();
System.out.println("result = " + result);
}
```
**运行结果**
![](https://img.mercymodest.com/public/20210122155329.png)
##### `handler`
![](https://img.mercymodest.com/public/20210114115620.png)
**运行完成时,对结果的处理。这里的完成时有两种情况,一种是正常执行,
返回值。另外一种是遇到异常抛出造成程序的中断(如果我们不在 handle CompletableFuture中处理异常的话)。**
**单元测试**
```java
/**
* 测试 CompletableFuture:
* <pre>
* {@code
* handleAsync(
* BiFunction<? super T, Throwable, ? extends U> fn, Executor executor)
* }
* </pre>
*/
@Test
public void testHandlerAsync() throws ExecutionException, InterruptedException {
String resultStr = CompletableFuture.supplyAsync(() -> "success", THREAD_POOL_EXECUTOR)
.handleAsync((res, exception) -> {
if (StrUtil.isNotBlank(res)) {
return res;
}
return exception.getMessage();
}, THREAD_POOL_EXECUTOR)
.get();
System.out.println("resultStr = " + resultStr);
resultStr = CompletableFuture.supplyAsync(() -> {
if (true) {
throw new RuntimeException("a exception happened");
}
return "success";
}, THREAD_POOL_EXECUTOR)
.handleAsync((res, exception) -> {
if (StrUtil.isNotBlank(res)) {
return res;
}
return exception.getMessage();
}, THREAD_POOL_EXECUTOR)
.get();
System.out.println("resultStr = " + resultStr);
}
```
**运行结果**
![](https://img.mercymodest.com/public/20210122155513.png)
\ No newline at end of file
# 蛋壳创意科技-技术分享-Java常用并发工具类(讲稿版)
# 蛋壳创意科技-技术分享-Java常用并发工具类(讲稿版)
> company: **[蛋壳创意科技](https://www.dankal.cn/)**
>
> code: ** []()**
## 分享议题
> - **Fork/Join框架**
> - **Java常用并发工具类**
> - **组合式异步编程: CompletableFuture**
## `Fork-Join`
<b> java 下多线程的开发可以我们自己启用多线程,线程池,还可以使用forkjoin,
forkjoin 可以让我们不去了解诸如Thread,Runnable 等相关的知识,只要遵循
forkjoin 的开发模式,就可以写出很好的多线程并发程序</b>
### 什么是分而治之
> ​ 同时forkjoin 在处理某一类问题时非常的有用,哪一类问题?分而治之的问
> 题。
>
> 十大计算机经典算法
>
> - 快速排序
>
> - 堆排序
>
> - 归并排序
>
> - 二分查找
>
> - 线性查找、
> 深度优先
>
> - 广度优先
>
> - Dijkstra
>
> - 动态规划
>
> - 朴素贝叶斯分类
>
> 有几个属于分而治之?
>
> ​ 3 个
>
> - 快速排序
>
> - 归并排序
>
> - 二分查找
>
> 分治法的设计思想是:将一个难以直接解决的大问题,分割成一些规模较小的相同问题,以便各个击破,分而治之。
>
> ​ 分治策略是:对于一个规模为n 的问题,若该问题可以容易地解决(比如说规模n 较小)则直接解决,否则将其分解为k 个规模较小的子问题,这些子问题互相独立且与原问题形式相同(子问题相互之间有联系就会变为动态规范算法),递归地解这些子问题,然后将各子问题的解合并得到原问题的解。这种算法设计策略叫做分治法。
### `Fork-Join`原理
![](https://img.mercymodest.com/public/20210112142502.jpg)
#### 工作密取
​ 即当前线程的Task 已经全被执行完毕,则自动取到其他线程的Task 池中取
出Task 继续执行。
​ ForkJoinPool 中维护着多个线程(一般为CPU 核数)在不断地执行Task,每
个线程除了执行自己职务内的Task 之外,还会根据自己工作线程的闲置情况去
获取其他繁忙的工作线程的Task,如此一来就能能够减少线程阻塞或是闲置的时
间,提高CPU 利用率。
![](https://img.mercymodest.com/public/20210112142618.jpg)
### `Fork-Join`编码实现
#### `Fork-Join`使用的标准范式
> ​ 我们要使用ForkJoin 框架,必须首先创建一个ForkJoin 任务。它提供在任务
> 中执行fork 和join 的操作机制,通常我们不直接继承ForkjoinTask 类,只需要直
> 接继承其子类。
>
> 1. RecursiveAction,用于没有返回结果的任务
> 2. RecursiveTask,用于有返回值的任务
> task 要通过ForkJoinPool 来执行,使用submit 或invoke 提交,两者的区
> 别是:invoke 是同步执行,调用之后需要等待任务完成,才能执行后面的代码;
> submit 是异步执行。
> join()和get 方法当任务完成的时候返回计算结果。
![](https://img.mercymodest.com/public/20210112142903.jpg)
​ 在我们自己实现的compute 方法里,首先需要判断任务是否足够小,如果足够小就直接执行任务。如果不足够小,就必须分割成两个子任务,每个子任务在调用invokeAll 方法时,又会进入compute 方法,看看当前子任务是否需要继续分割成孙任务,如果不需要继续分割,则执行当前子任务并返回结果。使用join方法会等待子任务执行完并得到其结果。
### `ForkJoin: RecursiveTask<V>`
**任务有返回值**
#### **使用ForkJoin简单实现归并排序.**
```java
package cn.dankal.share.forkjoin;
import cn.dankal.share.common.InsertionSort;
import cn.dankal.share.common.MergeSort;
import java.util.Arrays;
import java.util.concurrent.RecursiveTask;
/**
* 基于 {@link RecursiveTask} (ForkJoin) 简单实现 数组排序(归并排序)
*
* @author ZGH.MercyModest
* @version V1.0.0
* @create 2021-01-02
*/
public class IntArraySortTask extends RecursiveTask<int[]> {
/**
* 需要排序的数组
*/
private final int[] intArray;
/**
* 数组拆分临界值(length)
*/
private final int thresholdLength;
public IntArraySortTask(int[] intArray, int thresholdLength) {
this.intArray = intArray;
this.thresholdLength = thresholdLength;
}
@Override
protected int[] compute() {
if (intArray.length <= thresholdLength) {
// 达到拆分临界 直接排序
return InsertionSort.doAscSort(intArray);
} else {
// formIndex ... middle ... toIndex
// 任务拆分 将排序任务拆分成两个或更多
// 此处演示 拆分为 左右两个任务
int middle = intArray.length / 2;
// 将目标数组从 “中间”拆分
int[] leftIntArray = Arrays.copyOfRange(intArray, 0, middle);
int[] rightIntArray = Arrays.copyOfRange(intArray, middle, intArray.length);
// 拆分后,分为 左,右两个子Task
IntArraySortTask leftSortTask = new IntArraySortTask(leftIntArray, thresholdLength);
IntArraySortTask rightSortTask = new IntArraySortTask(rightIntArray, thresholdLength);
//执行所有 Task
invokeAll(leftSortTask, rightSortTask);
// 归并 left 的结果
int[] leftArray = leftSortTask.join();
// 归并 right 的结果
int[] rightArray = rightSortTask.join();
// 合并 归并结果
return MergeSort.mergeSortedArray(leftArray, rightArray);
}
}
}
```
##### ForkJoin 性能评测: 归并排序实现
**对比对象:**
- **通常归并排序**
- **Arrays.sort()**:DualPivotQuicksort 快速排序算法
**测试条件**
`3000000` int 数据 进行排序
###### 单元测试
```java
/**
* 测试 归并排序对比
*/
@Test
public void testMergeSortThreeWays() {
int[] generateIntArray = IntArrayFactory.generateIntArray(3000000);
System.out.println("数组元素: " + generateIntArray.length);
// general merge sort
final long generalMergeSortStart = System.currentTimeMillis();
MergeSort.sort(generateIntArray);
System.out.println("运行耗时: " + (System.currentTimeMillis() - generalMergeSortStart) + " 毫秒 (general mergeSort)");
// jdk utils: Arrays#sort
final long arraySortStartMs = System.currentTimeMillis();
// DualPivotQuicksort 快排
Arrays.sort(generateIntArray);
System.out.println("运行耗时: " + (System.currentTimeMillis() - arraySortStartMs) + " 毫秒 (Arrays#sort)");
// Fork/Join merge sort
ForkJoinPool forkJoinPool = new ForkJoinPool();
IntArraySortTask intArraySortTask = new IntArraySortTask(generateIntArray, 300);
final long forkJoinStartMs = System.currentTimeMillis();
forkJoinPool.invoke(intArraySortTask);
System.out.println("运行耗时: " + (System.currentTimeMillis() - forkJoinStartMs) + " 毫秒 (Fork/Join mergeSort)");
}
```
###### 运行结果
![](https://img.mercymodest.com/public/20210113115039.png)
**次要运行结果部分截图**
![](https://img.mercymodest.com/public/20210113144637.png)
**主要运行结果截图**
![](https://img.mercymodest.com/public/20210113144642.png)
### `ForkJoin:RecursiveAction`
**任务无返回值**
#### 使用 Fork/Join 框架实现目标文件搜索
##### `FindFileTask.java`
```java
package cn.dankal.share.forkjoin;
import cn.hutool.core.collection.CollectionUtil;
import cn.hutool.core.io.FileUtil;
import cn.hutool.core.util.ArrayUtil;
import java.io.File;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.RecursiveAction;
import java.util.concurrent.atomic.AtomicInteger;
/**
* 基于 {@link RecursiveAction} (ForkJoin)实现 文件查找与简单统计
*
* @author ZGH.MercyModest
* @version V1.0.0
* @create 2021-01-02
*/
public class FindFileTask extends RecursiveAction {
/**
* 目标文件夹
*/
private final String targetFolder;
/**
* 目标文件后缀
*/
private final String findFileSuffix;
/**
* 目标文件计数器
*/
public final static AtomicInteger COUNTER_TARGET_FILE = new AtomicInteger(0);
/**
* 遍历目录计数器
*/
public final static AtomicInteger COUNTER_EACH_FOLDER = new AtomicInteger(0);
public FindFileTask(String targetFolder, String findFileSuffix) {
this.targetFolder = targetFolder;
this.findFileSuffix = findFileSuffix;
}
@Override
protected void compute() {
// 参数校验
if (!FileUtil.exist(targetFolder) || FileUtil.isFile(targetFolder)) {
throw new IllegalArgumentException("targetFolder 必须是存在的 文件夹");
}
File targetFile = FileUtil.file(targetFolder);
File[] files = targetFile.listFiles();
if (ArrayUtil.isEmpty(files)) {
// 空文件夹直接返回
return;
}
// 需要执行 Task List
List<FindFileTask> subFindFileTask = new ArrayList<>(1 << 5);
for (File file : files) {
if (file.isDirectory()) {
// 文件夹
FindFileTask findFileTask = new FindFileTask(file.getAbsolutePath(), findFileSuffix);
subFindFileTask.add(findFileTask);
COUNTER_EACH_FOLDER.incrementAndGet();
} else {
// 文件
String fileSuffix = FileUtil.getSuffix(file);
if (("." + fileSuffix).equalsIgnoreCase(findFileSuffix)) {
// 是目标文件
System.out.println("find: " + file.getName());
COUNTER_TARGET_FILE.incrementAndGet();
}
}
}
if (CollectionUtil.isNotEmpty(subFindFileTask)) {
// 执行所有拆分的 task 如果需要的话
for (FindFileTask findFileTask : invokeAll(subFindFileTask)) {
// 因为是拆分 即使我们不需要获取拆分结果也需要 join 切切
findFileTask.join();
}
}
}
}
```
##### 单元测试
```java
/**
* 测试: Fork/Join RecursiveAction: 目标文件查找
*/
@Test
public void testFindFileByRecursiveAction() {
final String targetFolder = "C:/dev/MavenRepository";
final String findFileSuffix = ".lastUpdated";
FindFileTask findFileTask = new FindFileTask(targetFolder, findFileSuffix);
ForkJoinPool forkJoinPool = new ForkJoinPool();
forkJoinPool.invoke(findFileTask);
System.out.printf("共遍历 : %d 个文件夹%n", FindFileTask.COUNTER_EACH_FOLDER.get());
System.out.printf("找到目标文件 %s : %d 个%n", findFileSuffix, FindFileTask.COUNTER_TARGET_FILE.get());
}
```
##### 运行结果
![](https://img.mercymodest.com/public/20210113150306.png)
## 常用并发工具类
### Worker 模拟: 测试 `CountDownLatch`和`CyclicBarrier`
**Worker.java**
```java
package cn.dankal.share.concurrentutil;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.CyclicBarrier;
import java.util.concurrent.TimeUnit;
/**
* {@link Runnable}
* Worker 简单模拟
* <p>
* 用于测试 {@link CountDownLatch} 和 {@link CyclicBarrier} 的简单使用
* </p>
*
* @author ZGH.MercyModest
* @version V1.0.0
* @create 2021-01-02
*/
public class Worker implements Runnable {
/**
* 工作内容
*/
private final String workContent;
/**
* 工作消耗时间:单位 second
*/
private final long workTimeInMilliSecond;
/**
* <code>CountDownLatch</code>
*/
private CountDownLatch countDownLatch;
/**
* <code>CyclicBarrier</code>
*/
private CyclicBarrier cyclicBarrier;
public Worker(String workContent, long workTimeInMilliSecond, CountDownLatch countDownLatch) {
this.workContent = workContent;
this.workTimeInMilliSecond = workTimeInMilliSecond;
this.countDownLatch = countDownLatch;
}
public Worker(String workContent, long workTimeInMilliSecond, CyclicBarrier cyclicBarrier) {
this.workContent = workContent;
this.workTimeInMilliSecond = workTimeInMilliSecond;
this.cyclicBarrier = cyclicBarrier;
}
@Override
public void run() {
// 工作耗时
try {
TimeUnit.MILLISECONDS.sleep(workTimeInMilliSecond);
System.out.printf("%s 正在: %s 预计耗时 %d 毫秒 %n", Thread.currentThread().getName(), workContent, workTimeInMilliSecond);
} catch (InterruptedException e) {
// ignored
} finally {
if (null != this.countDownLatch) {
// 计数扣减
this.countDownLatch.countDown();
} else if (null != cyclicBarrier) {
try {
// 等待全部执行完成
this.cyclicBarrier.await();
} catch (Exception e) {
// ignored
}
}
}
}
}
```
#### CountDownLatch
##### 介绍
> ​ 闭锁,CountDownLatch 这个类能够使一个线程等待其他线程完成各自的工作后再执行。例如,应用程序的主线程希望在负责启动框架服务的线程已经启动所有的框架服务之后再执行。
> ​ CountDownLatch 是通过一个计数器来实现的,计数器的初始值为初始任务的数量。每当完成了一个任务后,计数器的值就会减1(CountDownLatch.countDown()方法)。当计数器值到达0 时,它表示所有的已经完成了任务,然后在闭锁上等待CountDownLatch.await()方法的线程就可以恢复执行任务。
> ​ 应用场景:
> ​ 实现最大的并行性:有时我们想同时启动多个线程,实现最大程度的并行性。例如,我们想测试一个单例类。如果我们创建一个初始计数为1 的CountDownLatch,并让所有线程都在这个锁上等待,那么我们可以很轻松地完成测试。我们只需调用一次countDown()方法就可以让所有的等待线程同时恢复执行。
> ​ 开始执行前等待n 个线程完成各自任务:例如应用程序启动类要确保在处理
> 用户请求前,所有N 个外部系统已经启动和运行了,例如处理excel 中多个表单。
>
> ![](https://img.mercymodest.com/public/20210113151952.png)
##### 模拟场景小析
> ```text
> 情景小析:
> 我们需要做这样一件事情:
> target: 获取商品详情
> - 获取商品信息 耗时 50 毫秒
> - 获取商品价格 耗时 10 毫秒
> - 商品属性获取并归并(Sku/Spu) 耗时 65 毫秒
> - 假设三个子任务之间没有依赖关系
> ```
##### `CountDownLatchTest.java`
```java
package cn.dankal.share.concurrentutil;
import cn.dankal.share.common.ThreadExecutorFactory;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.ThreadPoolExecutor;
/**
* CountDownLatch 简单使用测试
*
* @author ZGH.MercyModest
* @version V1.0.0
* @create 2021-01-02
*/
public class CountDownLatchTest {
public static void main(String[] args) {
ThreadPoolExecutor threadPoolExecutor = ThreadExecutorFactory.getThreadPoolExecutor("countDownLatch-test");
try {
/*
情景小析:
我们需要做这样一件事情:
target: 获取商品详情
- 获取商品信息 耗时 50 毫秒
- 获取商品价格 耗时 10 毫秒
- 商品属性获取并归并(Sku/Spu) 耗时 65 毫秒
- 假设三个子任务之间没有依赖关系
*/
// target: 获取商品详细
CountDownLatch countDownLatch = new CountDownLatch(3);
final long startTime = System.currentTimeMillis();
// 获取商品信息 50 毫秒
final long getProductDetailElapsed = 50;
Worker getProductDetailWorker = new Worker("获取商品信息", getProductDetailElapsed, countDownLatch);
threadPoolExecutor.execute(getProductDetailWorker);
// 获取商品价格 10 毫秒
final long getPriceElapsed = 10;
Worker getProductPrice = new Worker("获取商品价格", getPriceElapsed, countDownLatch);
threadPoolExecutor.execute(getProductPrice);
//商品属性获取并归并 65毫秒
final long productPropertiesReduceElapsed = 65;
Worker productPropertiesReduceWorker = new Worker("商品属性获取并归并", productPropertiesReduceElapsed, countDownLatch);
threadPoolExecutor.execute(productPropertiesReduceWorker);
try {
System.out.printf("%s 线程即将开始等待 ... ...%n", Thread.currentThread().getName());
countDownLatch.await();
} catch (InterruptedException e) {
// ignored
}
System.out.println("接口返回~");
System.out.println("接口放回理论耗时: " + (getProductDetailElapsed + getPriceElapsed + productPropertiesReduceElapsed) + " 毫秒");
System.out.println("接口放回实际耗时: " + (System.currentTimeMillis() - startTime) + " 毫秒");
} finally {
threadPoolExecutor.shutdown();
}
}
}
```
##### 运行结果
![](https://img.mercymodest.com/public/20210113150908.png)
#### CyclicBarrier
##### 介绍
> ​ CyclicBarrier 的字面意思是可循环使用(Cyclic)的屏障(Barrier)。它要做的事情是,让一组线程到达一个屏障(也可以叫同步点)时被阻塞,直到最后一个线程到达屏障时,屏障才会开门,所有被屏障拦截的线程才会继续运行。
> ​ CyclicBarrier 默认的构造方法是CyclicBarrier(int parties),其参数表示屏障拦截的线程数量,每个线程调用await 方法告诉CyclicBarrier 我已经到达了屏障,然后当前线程被阻塞。
> ​ CyclicBarrier 还提供一个更高级的构造函数CyclicBarrie(r int parties,Runnable
> barrierAction),用于在线程到达屏障时,优先执行barrierAction,方便处理更复杂的业务场景。
> ​ CyclicBarrier 可以用于多线程计算数据,最后合并计算结果的场景。
>
> ![](https://img.mercymodest.com/public/20210113152210.png)
##### 模拟场景小析
> ```text
> 情景小析:
> 我们需要做这样一件事情:
> target: 获取商品详情
> - 获取商品信息 耗时 50 毫秒
> - 获取商品价格 耗时 10 毫秒
> - 商品属性获取并归并(Sku/Spu) 耗时 65 毫秒
> - 假设三个子任务之间没有依赖关系
> ```
##### `CyclicBarrierTest.java`
```java
package cn.dankal.share.concurrentutil;
import cn.dankal.share.common.ThreadExecutorFactory;
import java.util.concurrent.CyclicBarrier;
import java.util.concurrent.ThreadPoolExecutor;
/**
* CyclicBarrier 简单使用测试
*
* @author ZGH.MercyModest
* @version V1.0.0
* @create 2021-01-02
*/
public class CyclicBarrierTest {
public static void main(String[] args) {
ThreadPoolExecutor threadPoolExecutor = ThreadExecutorFactory.getThreadPoolExecutor("countDownLatch-test");
try {
/*
情景小析:
我们需要做这样一件事情:
target: 获取商品详情
- 获取商品信息 耗时 50 毫秒
- 获取商品价格 耗时 10 毫秒
- 商品属性获取并归并(Sku/Spu) 耗时 65 毫秒
- 假设三个子任务之间没有依赖关系
*/
// target: 获取商品详细
CyclicBarrier cyclicBarrier = new CyclicBarrier(4);
final long startTime = System.currentTimeMillis();
// 获取商品信息 50 毫秒
final long getProductDetailElapsed = 50;
Worker getProductDetailWorker = new Worker("获取商品信息", getProductDetailElapsed, cyclicBarrier);
threadPoolExecutor.execute(getProductDetailWorker);
// 获取商品价格 10 毫秒
final long getPriceElapsed = 10;
Worker getProductPrice = new Worker("获取商品价格", getPriceElapsed, cyclicBarrier);
threadPoolExecutor.execute(getProductPrice);
//商品属性获取并归并 65毫秒
final long productPropertiesReduceElapsed = 65;
Worker productPropertiesReduceWorker = new Worker("商品属性获取并归并", productPropertiesReduceElapsed, cyclicBarrier);
threadPoolExecutor.execute(productPropertiesReduceWorker);
try {
System.out.printf("%s 线程即将开始等待 ... ...%n", Thread.currentThread().getName());
cyclicBarrier.await();
} catch (Exception e) {
// ignored
}
System.out.println("接口返回~");
System.out.println("接口放回理论耗时: " + (getProductDetailElapsed + getPriceElapsed + productPropertiesReduceElapsed) + " 毫秒");
System.out.println("接口放回实际耗时: " + (System.currentTimeMillis() - startTime) + " 毫秒");
} finally {
threadPoolExecutor.shutdown();
}
}
}
```
##### 运行结果
![](https://img.mercymodest.com/public/20210113151050.png)
### CountDownLatch和CyclicBarrier
> ​ CountDownLatch 的计数器只能使用一次,而CyclicBarrier 的计数器可以反复
> 使用。
> ​ CountDownLatch.await 一般阻塞工作线程,所有的进行预备工作的线程执行
> countDown,而CyclicBarrier 通过工作线程调用await 从而自行阻塞,直到所有工
> 作线程达到指定屏障,再大家一起往下走。
> ​ 在控制多个线程同时运行上,CountDownLatch 可以不限线程数量,而CyclicBarrier 是固定线程数。同时,CyclicBarrier 还可以提供一个barrierAction,合并多线程计算结果。
### Semaphore
#### 介绍
> ​ Semaphore(信号量)是用来控制同时访问特定资源的线程数量,它通过协调各个线程,以保证合理的使用公共资源。应用场景Semaphore 可以用于做流量控制,特别是公用资源有限的应用场景,比如数据库连接。假如有一个需求,要读取几万个文件的数据,因为都是IO 密集型任务,我们可以启动几十个线程并发读取,但是如果读到内存后,还需要存储到数据库中,而数据库的连接数只有10 个,这时我们必须控制只有10个线程同时获取数据库连接保存数据,否则会报错无法获取数据库连接。这个时候,就可以使用Semaphore 来做流量控制。Semaphore 的构造方法Semaphore(int permits)接受一个整型的数字,表示可用的许可证数量。Semaphore 的用法也很简单,首先线程使用Semaphore的acquire()方法获取一个许可证,使用完之后调用release()方法归还许可证。还可以用tryAcquire()方法尝试获取许可证。
>
> Semaphore 还提供一些其他方法,简略介绍如下。
>
> - intavailablePermits():返回此信号量中当前可用的许可证数。
> - intgetQueueLength():返回正在等待获取许可证的线程数。
> - booleanhasQueuedThreads():是否有线程正在等待获取许可证。
> - void reducePermits(int reduction):减少reduction 个许可证,是个protected方法。
> - Collection getQueuedThreads():返回所有等待获取许可证的线程集合,是个protected 方法。
>
> ![](https://img.mercymodest.com/public/20210113152835.png)
#### 模拟场景小析
**使用 Semaphore 简单实现令牌桶限流**
#### `TokenBucket.java`
```java
package cn.dankal.share.concurrentutil;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.Semaphore;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicInteger;
/**
* token Bucket 简单模拟
* <p>
* 基于 {@link Semaphore} 简单实现
* </p>
*
* @author ZGH.MercyModest
* @version V1.0.0
* @create 2021-01-02
*/
public class TokenBucket {
/**
* <code>Semaphore</code>
*/
private final Semaphore semaphore;
/**
* <code>CountDownLatch</code>
*/
private final CountDownLatch countDownLatch;
/**
* 令牌统计
*/
private final AtomicInteger tokenCounter = new AtomicInteger(0);
/**
* 成功线程数统计
*/
private final AtomicInteger successCounter = new AtomicInteger(0);
/**
* 失败线程数统计
*/
private final AtomicInteger fairCounter = new AtomicInteger(0);
public TokenBucket(Semaphore semaphore, CountDownLatch countDownLatch1) {
this.semaphore = semaphore;
this.countDownLatch = countDownLatch1;
}
/**
* 获取令牌
*/
public void getToken() {
try {
final long getTokenTimeout = 5;
if (semaphore.tryAcquire(getTokenTimeout, TimeUnit.MILLISECONDS)) {
// 成功获取到信号量: 获取令牌成功
// 保证 tryAcquire 超时
int tokenNum = tokenCounter.incrementAndGet();
TimeUnit.SECONDS.sleep(1);
System.out.printf("%s 成功获取到 %d 号令牌 %n", Thread.currentThread().getName(), tokenNum);
successCounter.incrementAndGet();
} else {
// 获取令牌失败
System.out.printf("%s 未获取到令牌 %n", Thread.currentThread().getName());
fairCounter.incrementAndGet();
}
} catch (InterruptedException e) {
// ignored
} finally {
// 成功/失败统计计数扣减
this.countDownLatch.countDown();
}
}
/**
* 成功获取令桶统计
*
* @return 成功获取令牌统计
*/
public int successCount() {
return successCounter.get();
}
/**
* 获取令牌失败统计
*
* @return 未成功获取令牌统计
*/
public int fairCount() {
return fairCounter.get();
}
}
```
#### `SemaphoreTest.java`
```java
package cn.dankal.share.concurrentutil;
import cn.dankal.share.common.ThreadExecutorFactory;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.Semaphore;
import java.util.concurrent.ThreadPoolExecutor;
/**
* Semaphore 简单使用测试
*
* @author ZGH.MercyModest
* @version V1.0.0
* @create 2021-01-02
*/
public class SemaphoreTest {
public static void main(String[] args) {
ThreadPoolExecutor threadPoolExecutor = ThreadExecutorFactory.getThreadPoolExecutor("semaphore-test");
// semaphore: 令牌桶只有 3 个令牌
Semaphore semaphore = new Semaphore(3);
// 参与抢令牌的线程数
final int count = 10;
// 确保统计顺序执行完(成功/失败统计)
CountDownLatch successAndFairCountDownLatch = new CountDownLatch(count);
TokenBucket tokenBucket = new TokenBucket(semaphore, successAndFairCountDownLatch);
try {
for (int i = 0; i < count; i++) {
threadPoolExecutor.execute(tokenBucket::getToken);
}
successAndFairCountDownLatch.await();
System.out.println("参与线程数: " + count);
System.out.println("成功线程数:" + tokenBucket.successCount());
System.out.println("失败线程数:" + tokenBucket.fairCount());
} catch (InterruptedException e) {
// ignored
} finally {
threadPoolExecutor.shutdown();
}
}
}
```
#### 运行结果
![](https://img.mercymodest.com/public/20210113151337.png)
### Exchanger
#### 介绍
> ​ Exchanger(交换者)是一个用于线程间协作的工具类。Exchanger 用于进行线程间的数据交换。它提供一个同步点,在这个同步点,两个线程可以交换彼此的数据。这两个线程通过exchange 方法交换数据,如果第一个线程先执行exchange()方法,它会一直等待第二个线程也执行exchange 方法,当两个线程都到达同步点时,这两个线程就可以交换数据,将本线程生产出来的数据传递给对方。
>
> ![](https://img.mercymodest.com/public/20210113153358.png)
>
> 官方注释:
>
> ##### 原版:
>
> ```text
> A synchronization point at which threads can pair and swap elements within pairs. Each thread presents some object on entry to the exchange method, matches with a partner thread, and receives its partner's object on return. An Exchanger may be viewed as a bidirectional form of a SynchronousQueue. Exchangers may be useful in applications such as genetic algorithms and pipeline designs.
>
> Sample Usage:
> Here are the highlights of a class that uses an Exchanger to swap buffers between threads so that the thread filling the buffer gets a freshly emptied one when it needs it, handing off the filled one to the thread emptying the buffer.
> ```
>
> ##### 大意可能是:
>
> ```text
> 一个同步点,在这个点上,线程可以配对和交换配对内的元素。每个线程在进入交换方法时都会呈现一些对象,与伙伴线程进行匹配,并在返回时接收伙伴的对象。Exchanger可以看作是SynchronousQueue的一种双向形式。交换器在遗传算法和流水线设计等应用中可能很有用。
> 示例用法。
> 下面是一个使用Exchanger在线程之间交换缓冲区的类的亮点,这样当需要时,填充缓冲区的线程就会得到一个新的空的缓冲区,把填充的缓冲区交给清空缓冲区的线程。
> ```
>
> ##### 官方示例
>
> ![](https://img.mercymodest.com/public/20210113153732.png)
#### `ExchangerTest.java`
```java
package cn.dankal.share.concurrentutil;
import cn.dankal.share.common.ThreadExecutorFactory;
import java.util.HashMap;
import java.util.UUID;
import java.util.concurrent.Exchanger;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
/**
* {@link Exchanger} 的简单使用
*
* @author ZGH.MercyModest
* @version V1.0.0
* @create 2021-01-02
*/
public class ExchangerTest {
public static void main(String[] args) {
/*
应用场景:
- 在两个线程之间进行数据交换
- 流水线式数据处理
- 线程一 处理完数据之后,将数据交给 线程二继续处理
*/
ThreadPoolExecutor threadPoolExecutor = ThreadExecutorFactory.getThreadPoolExecutor("exchanger-test");
Exchanger<HashMap<String, Object>> mapExchanger = new Exchanger<>();
try {
// 线程一 先处理
threadPoolExecutor.execute(() -> {
final HashMap<String, Object> dataMap = new HashMap<>(1 << 5);
dataMap.put(Thread.currentThread().getName(), UUID.randomUUID().toString().replace("-", ""));
System.out.println(Thread.currentThread().getName() + " 开始处理数据");
try {
TimeUnit.SECONDS.sleep(1);
System.out.printf("%s 线程 原始数据: %s%n", Thread.currentThread().getName(), dataMap);
System.out.println(Thread.currentThread().getName() + " 完成数据处理,即将进行数据传递");
HashMap<String, Object> exchangeResult = mapExchanger.exchange(dataMap);
System.out.printf("%s 获取交换后的结果 %s%n", Thread.currentThread().getName(), exchangeResult);
} catch (InterruptedException e) {
// ignored
}
});
// 线程二 继续处理
threadPoolExecutor.execute(() -> {
System.out.println(Thread.currentThread().getName() + " 开始处理数据");
try {
HashMap<String, Object> emptyMap = new HashMap<>(1);
TimeUnit.SECONDS.sleep(1);
System.out.printf("%s 线程 原始数据: %s%n", Thread.currentThread().getName(), emptyMap);
System.out.println(Thread.currentThread().getName() + " 完成数据处理,即将进行数据传递");
HashMap<String, Object> exchangeResult = mapExchanger.exchange(emptyMap);
System.out.printf("%s 获取交换后的结果 %s%n", Thread.currentThread().getName(), exchangeResult);
} catch (InterruptedException e) {
// ignored
}
});
} finally {
threadPoolExecutor.shutdown();
}
}
}
```
## 组合式异步编程: `CompletableFuture`
![](https://img.mercymodest.com/public/20210113162009.png)
### `Future`的不足
> ​ Future 是Java 5 添加的类,用来描述一个异步计算的结果。你可以使用isDone方法检查计算是否完成,或者使用get 阻塞住调用线程,直到计算完成返回结果,你也可以使用cancel 方法停止任务的执行。虽然Future 以及相关使用方法提供了异步执行任务的能力,但是对于结果的获取却是很不方便,只能通过阻塞或者轮询的方式得到任务的结果。阻塞的方式显然和我们的异步编程的初衷相违背,轮询的方式又会耗费无谓的CPU 资源,而且也不能及时地得到计算结果,为什么不能用观察者设计模式当计算结果完成及时通知监听者呢?。
> ​ Java 的一些框架,比如Netty,自己扩展了Java 的Future 接口,提供了addListener 等多个扩展方法,Google guava 也提供了通用的扩展Future:ListenableFuture、SettableFuture 以及辅助类Futures 等,方便异步编程。
> ​ 同时Future 接口很难直接表述多个Future 结果之间的依赖性。实际开发中,我们经常需要达成以下目的:
> ​ 将两个异步计算合并为一个——这两个异步计算之间相互独立,同时第二个
> 又依赖于第一个的结果。
> ​ 等待Future 集合中的所有任务都完成。
> ​ 仅等待Future 集合中最快结束的任务完成(有可能因为它们试图通过不同
> 的方式计算同一个值),并返回它的结果。
> ​ 应对Future 的完成事件(即当Future 的完成事件发生时会收到通知,并
> 能使用Future 计算的结果进行下一步的操作,不只是简单地阻塞等待操作的结
> 果)
### `CompletableFuture`
> ​ JDK1.8 才新加入的一个实现类CompletableFuture,实现了Future<T>,CompletionStage<T>两个接口。实现了Future 接口,意味着可以像以前一样通过阻塞或者轮询的方式获得结果
### CompletableFuture 的创建
**除了直接new 出一个CompletableFuture 的实例,还可以通过工厂方法创建CompletableFuture 的实例**
#### 工厂方法
![](https://img.mercymodest.com/public/20210113162455.png)
> ​ Asynsc 表示异步,而supplyAsync 与runAsync 不同在与前者异步返回一个结果,后者是void.第二个函数第二个参数表示是用我们自己创建的线程池,否则采用默认的ForkJoinPool.commonPool()作为它的线程池。
### 获取CompletableFuture结果集
> - public T get()
>
> - public T get(long timeout, TimeUnit unit)
>
> - public T getNow(T valueIfAbsent)
>
> - public T join()
>
> ​ getNow 有点特殊,如果结果已经计算完则返回结果或者抛出异常,否则返回给定的valueIfAbsent 值。
> ​ join 返回计算的结果或者抛出一个unchecked 异常(CompletionException),它和get 对抛出的异常的处理有些细微的区别。
#### `get()`
![](https://img.mercymodest.com/public/20210113164218.png)
#### `get(long timeout, TimeUnit unit)`
![](https://img.mercymodest.com/public/20210113164238.png)
#### `getNow(T valueIfAbsent)`
![](https://img.mercymodest.com/public/20210113165130.png)
#### `join()`
![](https://img.mercymodest.com/public/20210113165414.png)
### CompletableFuture辅助方法
#### `allOf(CompletableFuture<?>... cfs)`
![](https://img.mercymodest.com/public/20210113165913.png)
##### 单元测试
```java
/**
* 测试 CompletableFuture:allOf(CompletableFuture<?>... cfs)
*/
@Test
public void testAllOf() {
Runnable runnable = () -> {
try {
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
// ignored
}
System.out.printf("线程 %s 完成工作%n", Thread.currentThread().getName());
};
CompletableFuture<Void> completableFuture01 = CompletableFuture.runAsync(runnable);
CompletableFuture<Void> completableFuture02 = CompletableFuture.runAsync(runnable);
CompletableFuture<Void> completableFuture03 = CompletableFuture.runAsync(runnable);
CompletableFuture<Void> completableFuture04 = CompletableFuture.runAsync(runnable);
CompletableFuture<Void> completableFuture05 = CompletableFuture.runAsync(runnable);
System.out.println("all completableFuture is starting the work or will start work");
CompletableFuture.allOf(completableFuture01, completableFuture02, completableFuture03, completableFuture04, completableFuture05)
.join();
System.out.println("completableFuture all finished the work");
}
```
##### 运行结果
![](https://img.mercymodest.com/public/20210113171254.png)
#### `anyOf(CompletableFuture<?>... cfs)`
![](https://img.mercymodest.com/public/20210113171422.png)
##### 单元测试
```java
/**
* 测试 CompletableFuture:anyOf(CompletableFuture<?>... cfs)
*/
@Test
public void testAnyOf() {
Runnable runnable = () -> {
try {
TimeUnit.SECONDS.sleep(RandomUtil.randomInt(1,10));
} catch (InterruptedException e) {
// ignored
}
System.out.printf("线程 %s 完成工作%n", Thread.currentThread().getName());
};
CompletableFuture<Void> completableFuture01 = CompletableFuture.runAsync(runnable);
CompletableFuture<Void> completableFuture02 = CompletableFuture.runAsync(runnable);
CompletableFuture<Void> completableFuture03 = CompletableFuture.runAsync(runnable);
CompletableFuture<Void> completableFuture04 = CompletableFuture.runAsync(runnable);
CompletableFuture<Void> completableFuture05 = CompletableFuture.runAsync(runnable);
System.out.println("all completableFuture is starting the work or will start work");
CompletableFuture.anyOf(completableFuture01, completableFuture02, completableFuture03, completableFuture04, completableFuture05)
.join();
System.out.println("completableFuture all finished the work");
}
```
##### 运行结果
![](https://img.mercymodest.com/public/20210113171657.png)
### CompletableFuture 测试用例
前面我们在测试 `CountdownLatch``Cyclicbarrier`的时候有如下需求
> ```text
> - 获取商品信息 耗时 50 毫秒
> - 获取商品价格 耗时 10 毫秒
> - 商品属性获取并归并(Sku/Spu) 耗时 65 毫秒
> ```
我们使用使用 `CountdownLatch``Cyclicbarrier` 实现上述需求的时候是假设 三者之间没有任何依赖关系.如果
上述需求变更为
> ```text
> - 获取商品信息 (A) 耗时 50 毫秒
> - 获取商品价格 (B) 耗时 10 毫秒
> - 商品属性获取并归并(Sku/Spu) (C) 耗时 65 毫秒
> 重要: 我们只有获取到了 '商品信息' 才能执行 '商品属性获取并归并',即 C 必须在 A 之后执行
> ```
显然 这个需求无法使用 `CountdownLatch``Cyclicbarrier`实现,但是我们借助 `CompletableFuture`API可以轻而易举实现.
#### 实现需求
> - 获取商品信息 (A) 耗时 120 毫秒
> - 获取商品价格 (B) 耗时 95 毫秒
> - 商品属性获取并归并(Sku/Spu) (C) 耗时 165 毫秒
> 重要: 我们只有获取到了 '商品信息' 才能执行 '商品属性获取并归并',即 C 必须在 A 之后执行
#### 编码实现
```java
package cn.dankal.share.util.completablefuture;
import cn.dankal.share.common.ThreadExecutorFactory;
import org.junit.jupiter.api.Test;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
import java.util.function.Supplier;
/**
* <p>
* <code>CompletableFuture</code> 测试
* </p>
*
* @author ZGH.MercyModest
* @version V1.0.0
* @create 2021-01-22
*/
public class CompletableFutureTest {
/*
需求:
- 获取商品信息 (A) 耗时 120 毫秒
- 获取商品价格 (B) 耗时 95 毫秒
- 商品属性获取并归并(Sku/Spu) (C) 耗时 165 毫秒
重要: 我们只有获取到了 '商品信息' 才能执行 '商品属性获取并归并',即 C 必须在 A 之后执行
*/
/**
* <code>CompletableFuture</code> 测试
*/
@Test
public void test() throws Exception {
ThreadPoolExecutor threadPoolExecutor = ThreadExecutorFactory.getThreadPoolExecutor("dankal-test-completableFuture");
// 获取商品信息 耗时: 120 毫秒
final long getProductElapsed = 120L;
Supplier<Integer> getProductSupplier = () -> {
try {
// do work
TimeUnit.MILLISECONDS.sleep(getProductElapsed);
} catch (InterruptedException e) {
// ignored
}
// 返回商品信息成功: 200
System.out.printf("%s 获取商品信息成功,即将返回%n", Thread.currentThread().getName());
return 200;
};
// 获取商品价格 耗时 95 毫秒
final long getProductPriceElapsed = 95L;
Supplier<Double> getProductPriceSupplier = () -> {
try {
// do work
TimeUnit.MILLISECONDS.sleep(getProductPriceElapsed);
} catch (InterruptedException e) {
// ignored
}
System.out.printf("%s 成功获取到商品价格即将开始返回%n", Thread.currentThread().getName());
return 135.53;
};
// 归并商品属性耗时 165 毫秒
final long getProductPropertiesElapsed = 165L;
//程序开始计时
final long startMs = System.currentTimeMillis();
// 获取 商品信息 并归并商品属性
CompletableFuture<Integer> productAboutCompletableFuture = CompletableFuture.supplyAsync(getProductSupplier, threadPoolExecutor)
.thenApplyAsync(previousResult -> {
System.out.printf("%s 获取到商品信息: %d 工耗时 %d 毫秒%n", Thread.currentThread().getName(), previousResult, (System.currentTimeMillis() - startMs));
try {
// do work
TimeUnit.MILLISECONDS.sleep(getProductPropertiesElapsed);
} catch (InterruptedException e) {
// ignored
}
System.out.printf("%s 完成商品属性归并,即将返回结果%n", Thread.currentThread().getName());
return previousResult * 2;
}, threadPoolExecutor);
CompletableFuture<Double> getProductPriceCompletableFuture = CompletableFuture.supplyAsync(getProductPriceSupplier, threadPoolExecutor);
// 等待程序执行完成
CompletableFuture.allOf(productAboutCompletableFuture, getProductPriceCompletableFuture).get();
System.out.println("程序执行完成:");
System.out.printf("\t运行耗时: %d %n", (System.currentTimeMillis() - startMs));
System.out.printf("\t理论耗时: %d %n", (getProductElapsed + getProductPropertiesElapsed + getProductPriceElapsed));
// 模拟 Service 方法返回
System.out.printf("获取商品详情: %d%n", productAboutCompletableFuture.get()/2);
System.out.printf("获取商品属性: %d%n", productAboutCompletableFuture.get());
System.out.printf("获取商品价格: %.2f RMB%n", getProductPriceCompletableFuture.get());
}
}
```
#### 运行结果
![](https://img.mercymodest.com/public/20210122165021.png)
\ No newline at end of file
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment