原始问题(带有完整代码)
(我保留了原始的完整代码,因为下面的答案与此完整代码相关。)
我目前正在研究有关节能软件工程的硕士论文项目,我想(粗略地)监控不同 Java 方法的能耗行为。
所需行为的简短描述:
我有一组 JMH 基准测试,我想使用 Intel Power Gadget API 对其进行分析。所需的行为应该如下所示:
- 在每个方法进行基准测试之前,应创建并启动一个新线程,该线程将执行分析部分。
- 在“主”线程的基准测试期间,分析器线程应读取样本,直到执行 JMH 拆卸方法。
我的问题:
代码按顺序执行,而不是所需的并发执行。 “分析阶段”应在 JMH 设置中开始,在 JMH 拆卸方法中结束。
我的代码:
该代码仍处于实验阶段且困惑。对此感到抱歉。我仍然保留所有调试打印语句用于记录目的。
BenchmarkProgram.java:
package Profiling;
public class BenchmarkProgram {
public static void main(String[] args) throws Exception{
org.openjdk.jmh.Main.main(args);
}
}
PowerGadgetProfiler.java:
package Profiling;
import BenchmarkContainers.BenchmarkContainer;
import java.lang.reflect.Method;
import java.time.LocalDateTime;
import java.util.ArrayList;
import java.util.Arrays;
public class PowerGadgetProfiler {
static{
System.loadLibrary("PowerGadgetProfiler");
System.out.println("THREAD " + Thread.currentThread().getId() + " LIBRARY LOADED SUCCESSFULLY");
}
public static int profileId = 0;
static int methodCounter = 0;
private static final String folderName = buildFolderName();
public static final Object profilerLock = new Object();
public static volatile boolean running = false;
private static final PowerGadgetProfiler instance = new PowerGadgetProfiler();
private static String[] getBenchmarkMethodsInOrder(){
ArrayList<String> methodNames = new ArrayList<>();
Method[] methods = BenchmarkContainer.class.getMethods();
for (Method method : methods) {
String name = method.toString();
if(name.contains("BenchmarkContainer")
&& !name.contains("initializeProfilerThread")
&& !name.contains("stopProfiling")){
methodNames.add(method.getName());
}
}
String[] methodArray = methodNames.toArray(new String[0]);
Arrays.sort(methodArray);
System.out.println("THREAD " + Thread.currentThread().getId() + " METHODS: " + Arrays.toString(methodArray));
return methodArray;
}
private static String buildFolderName(){
LocalDateTime timeStamp = LocalDateTime.now();
String result = "/Users/timerdar/BM-"; // change when on other machine
result += Integer.toString(timeStamp.getYear());
result += Integer.toString(timeStamp.getMonthValue());
result += Integer.toString(timeStamp.getDayOfMonth());
result += Integer.toString(timeStamp.getHour());
result += Integer.toString(timeStamp.getMinute());
return result;
}
private static String buildFileName(){
return folderName + "/" + benchmarkMethods[methodCounter] + profileId + ".csv";
}
private static final String[] benchmarkMethods = getBenchmarkMethodsInOrder();
private native void startProfiling(String fileName);
private native void readSample();
private native void stopProfiling();
public static void startProfiling(){
System.out.println("THREAD " + Thread.currentThread().getId() + " ENTERED START PROFILING");
synchronized(profilerLock) {
System.out.println("THREAD " + Thread.currentThread().getId() + " RELEASES PROFILER LOCK");
profilerLock.notify();
}
final String fileName = buildFileName();
instance.startProfiling(fileName);
System.out.println("THREAD " + Thread.currentThread().getId() + " CALLED PROFILE EXECUTION");
profileId++;
while(running){
getSample();
}
endProfiling();
}
private static void getSample(){
instance.readSample();
}
private static void endProfiling(){
System.out.println("THREAD " + Thread.currentThread().getId() + " ENTERED endProfiling()");
instance.stopProfiling();
System.out.println("JAVA THREAD " + Thread.currentThread().getId() + " CALLED NATIVE stopProfiling()");
methodCounter++;
}
}
BenchmarkContainer.java:
这只是我拥有的所有基准测试的一小部分。我减少了基准测试的数量,以减少创建概念验证时的开销。
package BenchmarkContainers;
import Profiling.PowerGadgetProfiler;
import Profiling.ProfilerThread;
import org.openjdk.jmh.annotations.*;
import java.util.concurrent.TimeUnit;
@State(Scope.Benchmark)
public class BenchmarkContainer {
//@Param({"5", "30", "210", "1470"})
@Param({"1", "2"})
public int upperBound;
@Setup(Level.Trial)
public void initializeProfilerThread() {
System.out.println("THREAD " + Thread.currentThread().getId() + " ENTERED initializeProfilerThread()");
ProfilerThread profilerThread = new ProfilerThread();
profilerThread.start();
synchronized (PowerGadgetProfiler.profilerLock){
try {
System.out.println("THREAD " + Thread.currentThread().getId() + " WAITS FOR PROFILER LOCK");
PowerGadgetProfiler.profilerLock.wait();
PowerGadgetProfiler.running = true;
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
@TearDown(Level.Trial)
public void stopProfiling(){
System.out.println("THREAD " + Thread.currentThread().getId() + " ENTERED TEARDOWN PROCEDURE");
synchronized (PowerGadgetProfiler.profilerLock){
PowerGadgetProfiler.running = false;
}
}
@Benchmark
@Fork(value = 1, warmups = 1)
@OutputTimeUnit(TimeUnit.NANOSECONDS)
@BenchmarkMode(Mode.AverageTime)
public void init(){
{
}
}
@Benchmark
@Fork(value = 1, warmups = 1)
@OutputTimeUnit(TimeUnit.NANOSECONDS)
@BenchmarkMode(Mode.AverageTime)
public int geoSum(){
int sum = 0;
for(int i = 0; i <= upperBound; i++){
sum += i;
}
return sum;
}
}
ProfilerThread.java:
package Profiling;
public class ProfilerThread extends Thread{
@Override
public void run() {
System.out.println("STARTING PROFILING IN PROFILER THREAD");
PowerGadgetProfiler.startProfiling();
}
}
Profiling_PowerGadgetProfiler.c:
#include <jni.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <IntelPowerGadget/EnergyLib.h>
#include <pthread.h>
#include "Profiling_PowerGadgetProfiler.h"
JNIEXPORT void JNICALL Java_Profiling_PowerGadgetProfiler_startProfiling
(JNIEnv *env, jobject thisObject, jstring fileName)
{
uint64_t tid;
pthread_threadid_np(NULL, &tid);
printf("THREAD %llu CALLED NATIVE startProfiling()\n", tid);
IntelEnergyLibInitialize();
printf("NATIVE PROFILER INITIALIZED\n");
char fileNameAsString[255];
const char *fileNameAsConstString = (*env)->GetStringUTFChars(env, fileName, 0);
strcpy(fileNameAsString, fileNameAsConstString);
printf("RESULTING FILE NAME: %s\n", fileNameAsString);
printf("STARTING PROFILING IN NATIVE CODE\n");
StartLog(fileNameAsString);
}
JNIEXPORT void JNICALL Java_Profiling_PowerGadgetProfiler_readSample
(JNIEnv *env , jobject thisObject)
{
ReadSample();
}
JNIEXPORT void JNICALL Java_Profiling_PowerGadgetProfiler_stopProfiling
(JNIEnv* env, jobject thisObject)
{
uint64_t tid;
pthread_threadid_np(NULL, &tid);
printf("THREAD %llu CALLED NATIVE stopProfiling()\n", tid);
printf("ENDING PROFILING IN NATIVE CODE AND WRITE TO FILE\n");
StopLog();
}
控制台输出:
请不要担心 JMH 和我的输出的混合。这对我来说并不重要。
# JMH version: 1.32
# VM version: JDK 14.0.1, OpenJDK 64-Bit Server VM, 14.0.1+7
# VM invoker: /Users/timerdar/Library/Java/JavaVirtualMachines/openjdk-14.0.1/Contents/Home/bin/java
# VM options: -Djava.library.path=/Users/timerdar/IdeaProjects/microbenchmark_test/src/main/java/Profiling -javaagent:/Applications/IntelliJ IDEA CE.app/Contents/lib/idea_rt.jar=62464:/Applications/IntelliJ IDEA CE.app/Contents/bin -Dfile.encoding=UTF-8
# Blackhole mode: full + dont-inline hint
# Warmup: 5 iterations, 10 s each
# Measurement: 5 iterations, 10 s each
# Timeout: 10 min per iteration
# Threads: 1 thread, will synchronize iterations
# Benchmark mode: Average time, time/op
# Benchmark: BenchmarkContainers.BenchmarkContainer.geoSum
# Parameters: (upperBound = 1)
# Run progress: 0,00% complete, ETA 00:13:20
# Warmup Fork: 1 of 1
# Warmup Iteration 1: THREAD 16 ENTERED initializeProfilerThread()
STARTING PROFILING IN PROFILER THREAD
THREAD 17 LIBRARY LOADED SUCCESSFULLY
THREAD 17 METHODS: [geoSum, init]
THREAD 16 WAITS FOR PROFILER LOCK
THREAD 17 ENTERED START PROFILING
THREAD 17 RELEASES PROFILER LOCK
THREAD 17 STARTING PROFILING IN PGP WITH PARAM VALUE: 1
THREAD 17 CALLED PROFILE EXECUTION
6,152 ns/op
# Warmup Iteration 2: 6,139 ns/op
# Warmup Iteration 3: 5,688 ns/op
# Warmup Iteration 4: 6,172 ns/op
# Warmup Iteration 5: 6,525 ns/op
Iteration 1: 6,858 ns/op
Iteration 2: 7,265 ns/op
Iteration 3: 7,523 ns/op
Iteration 4: 7,494 ns/op
Iteration 5: THREAD 16 ENTERED TEARDOWN PROCEDURE
7,776 ns/op
THREAD 17 ENTERED endProfiling()
THREAD 204730 CALLED NATIVE startProfiling()
NATIVE PROFILER INITIALIZED
RESULTING FILE NAME: /Users/timerdar/BM-202111301910/geoSum0.csv
STARTING PROFILING IN NATIVE CODE
THREAD 204730 CALLED NATIVE stopProfiling()
ENDING PROFILING IN NATIVE CODE AND WRITE TO FILE
到目前为止我尝试过的:
我将分析的控制流从 native 代码删除到了 Java 代码中。之前,我的 native 代码中有一个静态变量,它指示基准测试的运行状态。调用
ReadSample()
方法,直到此变量交换(C 文件中的其他函数)。我从
PowerGadgetProfiler
startProfiling
、getSample
和中的函数中删除了
synchronized
关键字>结束分析
感谢您的耐心等待,希望您能帮助我。
重现问题所需的最少代码示例:
我创建了一个最小工作示例,以将问题减少到其主要组件。
代码:
BenchmarkProgram.java
package Profiling;
public class BenchmarkProgram {
public static void main(String[] args) throws Exception{
org.openjdk.jmh.Main.main(args);
}
}
BenchmarkContainer.java
package BenchmarkContainers;
import Profiling.PowerGadgetProfiler;
import Profiling.ProfilerThread;
import org.openjdk.jmh.annotations.*;
@State(Scope.Benchmark)
public class BenchmarkContainer {
@Param({"1", "2"})
public int upperBound;
Thread profilerThread;
@Setup(Level.Trial)
public void initializeProfilerThread() {
profilerThread = new ProfilerThread();
profilerThread.start();
PowerGadgetProfiler.running = true;
}
@TearDown(Level.Trial)
public void stopProfiling() {
PowerGadgetProfiler.running = false;
try {
profilerThread.join();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
@Benchmark
@Fork(0)
public void init() {
{
}
}
@Benchmark
@Fork(0)
public int geoSum() {
int sum = 0;
for (int i = 0; i <= upperBound; i++) {
sum += i;
}
return sum;
}
}
PowerGadgetProfiler.java
package Profiling;
public class PowerGadgetProfiler {
static {
System.loadLibrary("PowerGadgetProfiler");
}
public static volatile boolean running = false;
private static native void peep();
public static void startProfiling() {
peep();
System.out.println("MOCK PEEP");
while (running) {
getSample();
}
endProfiling();
}
private static void getSample() {
// mock sampling
}
private static void endProfiling() {
peep();
System.out.println("MOCK PEEP");
}
}
ProfilerThread.java
package Profiling;
public class ProfilerThread extends Thread{
@Override
public void run() {
System.out.println("STARTING PROFILING IN PROFILER THREAD");
PowerGadgetProfiler.startProfiling();
}
}
Profiling_PowerGadgetProfiler.c
#include <jni.h>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <IntelPowerGadget/EnergyLib.h>
#include <pthread.h>
#include "Profiling_PowerGadgetProfiler.h"
JNIEXPORT void JNICALL Java_Profiling_PowerGadgetProfiler_peep
(JNIEnv *env, jclass thisClass)
{
uint64_t tid;
pthread_threadid_np(NULL, &tid);
printf("THREAD %llu PEEP\n", tid);
}
输出:
# JMH version: 1.32
# VM version: JDK 14.0.1, OpenJDK 64-Bit Server VM, 14.0.1+7
# VM invoker: /Users/timerdar/Library/Java/JavaVirtualMachines/openjdk-14.0.1/Contents/Home/bin/java
# VM options: -Djava.library.path=/Users/timerdar/IdeaProjects/microbenchmark_test/src/main/java/Profiling -javaagent:/Applications/IntelliJ IDEA CE.app/Contents/lib/idea_rt.jar=50458:/Applications/IntelliJ IDEA CE.app/Contents/bin -Dfile.encoding=UTF-8
# Blackhole mode: full + dont-inline hint
# Warmup: 5 iterations, 10 s each
# Measurement: 5 iterations, 10 s each
# Timeout: 10 min per iteration
# Threads: 1 thread, will synchronize iterations
# Benchmark mode: Throughput, ops/time
# Benchmark: BenchmarkContainers.BenchmarkContainer.geoSum
# Parameters: (upperBound = 1)
# Run progress: 0,00% complete, ETA 00:06:40
# Fork: N/A, test runs in the host VM
# *** WARNING: Non-forked runs may silently omit JVM options, mess up profilers, disable compiler hints, etc. ***
# *** WARNING: Use non-forked runs only for debugging purposes, not for actual performance runs. ***
# Warmup Iteration 1: STARTING PROFILING IN PROFILER THREAD
THREAD 14 LIBRARY LOADED SUCCESSFULLY
MOCK PEEP
351509573,446 ops/s
# Warmup Iteration 2: 368618416,430 ops/s
# Warmup Iteration 3: 318780225,551 ops/s
# Warmup Iteration 4: 316404930,396 ops/s
# Warmup Iteration 5: 308587428,671 ops/s
Iteration 1: 307622321,475 ops/s
Iteration 2: 295849314,405 ops/s
Iteration 3: 310904513,944 ops/s
Iteration 4: 298409293,008 ops/s
Iteration 5: MOCK PEEP
293730263,155 ops/s
Result "BenchmarkContainers.BenchmarkContainer.geoSum":
301303141,197 ±(99.9%) 29045283,861 ops/s [Average]
(min, avg, max) = (293730263,155, 301303141,197, 310904513,944), stdev = 7542967,981
CI (99.9%): [272257857,337, 330348425,058] (assumes normal distribution)
# JMH version: 1.32
# VM version: JDK 14.0.1, OpenJDK 64-Bit Server VM, 14.0.1+7
# VM invoker: /Users/timerdar/Library/Java/JavaVirtualMachines/openjdk-14.0.1/Contents/Home/bin/java
# VM options: -Djava.library.path=/Users/timerdar/IdeaProjects/microbenchmark_test/src/main/java/Profiling -javaagent:/Applications/IntelliJ IDEA CE.app/Contents/lib/idea_rt.jar=50458:/Applications/IntelliJ IDEA CE.app/Contents/bin -Dfile.encoding=UTF-8
# Blackhole mode: full + dont-inline hint
# Warmup: 5 iterations, 10 s each
# Measurement: 5 iterations, 10 s each
# Timeout: 10 min per iteration
# Threads: 1 thread, will synchronize iterations
# Benchmark mode: Throughput, ops/time
# Benchmark: BenchmarkContainers.BenchmarkContainer.geoSum
# Parameters: (upperBound = 2)
# Run progress: 25,00% complete, ETA 00:05:01
# Fork: N/A, test runs in the host VM
# *** WARNING: Non-forked runs may silently omit JVM options, mess up profilers, disable compiler hints, etc. ***
# *** WARNING: Use non-forked runs only for debugging purposes, not for actual performance runs. ***
# Warmup Iteration 1: STARTING PROFILING IN PROFILER THREAD
MOCK PEEP
250420430,065 ops/s
# Warmup Iteration 2: 240512982,879 ops/s
# Warmup Iteration 3: 234267496,579 ops/s
# Warmup Iteration 4: 232677741,065 ops/s
# Warmup Iteration 5: 225373803,499 ops/s
Iteration 1: 225697770,075 ops/s
Iteration 2: 233106892,737 ops/s
Iteration 3: 230332046,340 ops/s
Iteration 4: 220403129,274 ops/s
Iteration 5: MOCK PEEP
222970983,188 ops/s
Result "BenchmarkContainers.BenchmarkContainer.geoSum":
226502164,323 ±(99.9%) 20064492,546 ops/s [Average]
(min, avg, max) = (220403129,274, 226502164,323, 233106892,737), stdev = 5210684,997
CI (99.9%): [206437671,777, 246566656,869] (assumes normal distribution)
# JMH version: 1.32
# VM version: JDK 14.0.1, OpenJDK 64-Bit Server VM, 14.0.1+7
# VM invoker: /Users/timerdar/Library/Java/JavaVirtualMachines/openjdk-14.0.1/Contents/Home/bin/java
# VM options: -Djava.library.path=/Users/timerdar/IdeaProjects/microbenchmark_test/src/main/java/Profiling -javaagent:/Applications/IntelliJ IDEA CE.app/Contents/lib/idea_rt.jar=50458:/Applications/IntelliJ IDEA CE.app/Contents/bin -Dfile.encoding=UTF-8
# Blackhole mode: full + dont-inline hint
# Warmup: 5 iterations, 10 s each
# Measurement: 5 iterations, 10 s each
# Timeout: 10 min per iteration
# Threads: 1 thread, will synchronize iterations
# Benchmark mode: Throughput, ops/time
# Benchmark: BenchmarkContainers.BenchmarkContainer.init
# Parameters: (upperBound = 1)
# Run progress: 50,00% complete, ETA 00:03:20
# Fork: N/A, test runs in the host VM
# *** WARNING: Non-forked runs may silently omit JVM options, mess up profilers, disable compiler hints, etc. ***
# *** WARNING: Use non-forked runs only for debugging purposes, not for actual performance runs. ***
# Warmup Iteration 1: STARTING PROFILING IN PROFILER THREAD
MOCK PEEP
1285734648,964 ops/s
# Warmup Iteration 2: 1281687199,566 ops/s
# Warmup Iteration 3: 1248636633,953 ops/s
# Warmup Iteration 4: 1286229544,274 ops/s
# Warmup Iteration 5: 1242773248,106 ops/s
Iteration 1: 1249716889,546 ops/s
Iteration 2: 1163957364,456 ops/s
Iteration 3: 1165338083,544 ops/s
Iteration 4: 1175806686,372 ops/s
Iteration 5: MOCK PEEP
1206242463,819 ops/s
Result "BenchmarkContainers.BenchmarkContainer.init":
1192212297,547 ±(99.9%) 140077359,758 ops/s [Average]
(min, avg, max) = (1163957364,456, 1192212297,547, 1249716889,546), stdev = 36377645,494
CI (99.9%): [1052134937,790, 1332289657,305] (assumes normal distribution)
# JMH version: 1.32
# VM version: JDK 14.0.1, OpenJDK 64-Bit Server VM, 14.0.1+7
# VM invoker: /Users/timerdar/Library/Java/JavaVirtualMachines/openjdk-14.0.1/Contents/Home/bin/java
# VM options: -Djava.library.path=/Users/timerdar/IdeaProjects/microbenchmark_test/src/main/java/Profiling -javaagent:/Applications/IntelliJ IDEA CE.app/Contents/lib/idea_rt.jar=50458:/Applications/IntelliJ IDEA CE.app/Contents/bin -Dfile.encoding=UTF-8
# Blackhole mode: full + dont-inline hint
# Warmup: 5 iterations, 10 s each
# Measurement: 5 iterations, 10 s each
# Timeout: 10 min per iteration
# Threads: 1 thread, will synchronize iterations
# Benchmark mode: Throughput, ops/time
# Benchmark: BenchmarkContainers.BenchmarkContainer.init
# Parameters: (upperBound = 2)
# Run progress: 75,00% complete, ETA 00:01:40
# Fork: N/A, test runs in the host VM
# *** WARNING: Non-forked runs may silently omit JVM options, mess up profilers, disable compiler hints, etc. ***
# *** WARNING: Use non-forked runs only for debugging purposes, not for actual performance runs. ***
# Warmup Iteration 1: STARTING PROFILING IN PROFILER THREAD
MOCK PEEP
1167710836,896 ops/s
# Warmup Iteration 2: 1206785568,628 ops/s
# Warmup Iteration 3: 1160517470,177 ops/s
# Warmup Iteration 4: 1192993000,343 ops/s
# Warmup Iteration 5: 1198394027,257 ops/s
Iteration 1: 1158210173,102 ops/s
Iteration 2: 1169432730,215 ops/s
Iteration 3: 1070907177,608 ops/s
Iteration 4: 336332917,114 ops/s
Iteration 5: MOCK PEEP
433452372,375 ops/s
Result "BenchmarkContainers.BenchmarkContainer.init":
833667074,083 ±(99.9%) 1589834232,333 ops/s [Average]
(min, avg, max) = (336332917,114, 833667074,083, 1169432730,215), stdev = 412874901,402
CI (99.9%): [≈ 0, 2423501306,415] (assumes normal distribution)
# Run complete. Total time: 00:06:40
REMEMBER: The numbers below are just data. To gain reusable insights, you need to follow up on
why the numbers are the way they are. Use profilers (see -prof, -lprof), design factorial
experiments, perform baseline and negative tests that provide experimental control, make sure
the benchmarking environment is safe on JVM/OS/HW level, ask for reviews from the domain experts.
Do not assume the numbers tell you what you want them to tell.
Benchmark (upperBound) Mode Cnt Score Error Units
BenchmarkContainers.BenchmarkContainer.geoSum 1 thrpt 5 301303141,197 ± 29045283,861 ops/s
BenchmarkContainers.BenchmarkContainer.geoSum 2 thrpt 5 226502164,323 ± 20064492,546 ops/s
BenchmarkContainers.BenchmarkContainer.init 1 thrpt 5 1192212297,547 ± 140077359,758 ops/s
BenchmarkContainers.BenchmarkContainer.init 2 thrpt 5 833667074,083 ± 1589834232,333 ops/s
THREAD 62778 PEEP
THREAD 62778 PEEP
THREAD 63451 PEEP
THREAD 63451 PEEP
THREAD 63828 PEEP
THREAD 63828 PEEP
THREAD 64242 PEEP
THREAD 64242 PEEP
Process finished with exit code 0
预期行为: 通常我希望在 Java 代码继续运行之前先执行 native 代码。但是,该程序似乎将所有代码排队,然后在基准测试完成后立即执行所有内容。
正如 this Stackoverflow Thread 所说,程序通常应该按预期工作。但我目前无法识别代码中的问题。
下图描述了我期望的程序行为:
希望您能再次帮助我。
最佳答案
我在 IDE 中运行了该示例(摆脱了 native 内容)。而且它似乎工作正常。
因此,首先调用设置,然后调用基准测试,然后调用拆卸。这是您通常想要的预期顺序流程。所以我没有看到问题。
顺便说一句,我确实看到了一些并发问题。
synchronized (PowerGadgetProfiler.profilerLock){
try {
System.out.println("THREAD " + Thread.currentThread().getId() + " WAITS FOR PROFILER LOCK");
PowerGadgetProfiler.profilerLock.wait();
PowerGadgetProfiler.running = true;
} catch (InterruptedException e) {
e.printStackTrace();
}
}
profilerLock 等待可能会虚假失败,因此通常需要在循环中调用它。通常情况下,您还会等待分析器运行等条件;但在本例中,您正在设置条件。
所以我期待这样的事情:
synchronized (PowerGadgetProfiler.profilerLock){
while(!PowerGadgetProfiler.running){
PowerGadgetProfiler.profilerLock.wait();
}
}
我会像这样更新PowerGadgetProfiler.running
:
public static void startProfiling(){
synchronized(profilerLock) {
PowerGadgetProfiler.running=true;
profilerLock.notifyAll();
}
当您等待探查器终止时,应该应用类似的修复。
关于java - 为什么我的 native JNI 代码是顺序运行而不是并发运行?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/70174730/