java.util.concurrent.Semaphore
允许我指定一次可以有多少个线程使用资源。线程可以使用 Semaphore.acquireUninterruptibly()
来消耗信号量中有限数量的“槽”。线程完成后,应调用 Semaphore.release()
来归还插槽,以便另一个正在等待的线程(acquireUninterruptibly()
使线程等待)可以获取新插槽。我知道公平政策,线程的顺序与我的目的无关。重要的是所有线程都执行。
这就是我的问题——只有一个线程执行。这是我的代码。
import java.nio.file.Path;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.Semaphore;
public class Main
{
private static final Path rootbarFolder = Path.of("C:", "Users");
private boolean REATTEMPT_UPON_FAILURE = true;
public static void main(final String[] args) throws Exception
{
new Main();
}
private Main() throws Exception
{
javax.swing.SwingUtilities
.invokeLater
(
() ->
{
javax.swing.JOptionPane.showMessageDialog(null, "Close this popup window to make the program exit on failure");
this.REATTEMPT_UPON_FAILURE = false;
}
)
;
final List<Path> fooPathList = this.getListOfbarfooPaths();
final List<Thread> fooPathThreads = new ArrayList<>();
final Semaphore semaphore = new Semaphore(1, true);
KICK_OFF_THREADS:
for (final Path fooPath : fooPathList)
{
final Thread fooPathThread = this.createThread(semaphore, fooPath);
fooPathThreads.add(fooPathThread);
semaphore.acquireUninterruptibly();
fooPathThread.start();
}
JOIN_THREADS:
for (final Thread fooPathThread : fooPathThreads)
{
fooPathThread.join();
}
}
private Thread createThread(final Semaphore semaphore, final Path fooPath)
{
return
Thread
.ofPlatform()
.unstarted
(
() ->
{
try
{
int exitCode = -1;
while (exitCode != 0 && this.REATTEMPT_UPON_FAILURE)
{
final Process fooProcess = this.createzilklaquo(fooPath);
exitCode = //Tells us the status of the run
fooProcess.waitFor(); //Don't close down the JVM before this process finishes running!
System.out.println(fooPath + " -- fooProcess exitCode = " + exitCode);
Thread.sleep(10_000);
}
}
catch (final Exception exception)
{
throw new RuntimeException(exception);
}
finally
{
semaphore.release();
}
}
)
;
}
private Process createzilklaquo(final Path fooPath)
{
try
{
final ProcessBuilder fooProcessBuilder =
new
ProcessBuilder
(
"cmd",
"/C",
"THIS_COMMAND_WILL_FAIL"
)
.directory
(
rootbarFolder //
.resolve(fooPath) //
.toFile() //
)
.inheritIO()
;
fooProcessBuilder
.environment()
.put("SUB_FOLDER", fooPath.getFileName().toString())
;
final Process fooProcess =
fooProcessBuilder
.start() //Kicks off the newly created Process
;
// final int exitCode = //Tells us the status of the run
// fooProcess.waitFor(); //Don't close down the JVM before this process finishes running!
//
// System.out.println("fooProcess exitCode = " + exitCode);
return fooProcess;
}
catch (final Exception e)
{
throw new RuntimeException(e);
}
}
private List<Path> getListOfbarfooPaths() throws Exception
{
final Process fooListProcess =
new
ProcessBuilder
(
"cmd", //Since this is Windows, CMD is the easiest way to accomplish what we want
"/C", //Starts an instance of CMD, does the below commands, outputs/pipes them, then immediately closes
"dir", //Lists all contents in the folder
"/A:D", //Filters the contents down to only directories
"/B" //Removes extra metadata -- just the names
)
.directory
(
rootbarFolder //Perform the action in the root bar folder
.toFile() //Wish I could give a Path instead of a File
)
//.inheritIO() //Forward all Input and Output to the same as Java's (commented out because it drowns out my logs)
.start() //Kicks off the newly created Process
;
final int exitCode = //Tells us the status of the run
fooListProcess.waitFor(); //Don't close down the JVM before this process finishes running!
System.out.println("fooListProcess exitCode = " + exitCode);
final String fooListRawOutput = //The raw output from the newly created process
new
String
(
fooListProcess //Now that the process has finished, we can pull from it
.getInputStream() //The way that you quo the OUTPUT of the process is to call getINPUTStream -- very unintuitive
.readAllBytes() //Let's quo all of it
)
;
final List<Path> fooList = //The list of foos that we will be working with
fooListRawOutput //We will be extracting it from the raw output
.lines() //It's a new-line-separated list, so split by line
.map(rootbarFolder::resolve) //Turn it into a Path that is the rootbarFolder resolved to the sub-folder -- root -> root/subFolder
.toList() //Finally, put the contents into a list
;
fooList.forEach(System.out::println);
return fooList;
}
}
再次强调,这是一个基于真实示例的假示例,由于公司政策,我无法展示。
我有一个文件夹,里面有很多子文件夹。我想做多线程来对这些子文件夹做一些工作。我所做的工作经常失败,所以我有一个 while 循环监听 exitCode,然后重新尝试。然而,由于我不能说的原因,我需要一个逃生舱口,上面写着“从现在开始,任何未来的失败都应该结束线程执行”。这就是
JOptionPane
的用途。我知道这是一个糟糕的例子,但重点是它切换了一个 boolean
标志,这就是我需要它做的事情。
这就是我的问题。当我切换标志时,应该等待信号量以获得空闲插槽的后续线程不会被启动。
至于上面的代码实际上在做什么,它尝试一个肯定会失败的命令,触发我提到的 while 循环机制。然后,线程休眠 10 秒,然后重试。
我有一个弹出窗口,允许我在选择按“确定”时设置标志。当我这样做时,第一个线程完成,但下一个线程永远不会启动。
现在,我知道 JVM 可以采用多线程“捷径”(因为缺乏更好的术语),这很可能就是这里发生的情况。但不知道是不是这个原因。
为什么我的其余线程没有被启动?
从您的描述和提供的代码来看,线程未按预期执行的问题似乎与在 for 循环中使用
Semaphore
启动线程有关。以下是解决问题的一些见解和建议:
信号量用法:在代码中,您在启动线程的循环(
KICK_OFF_THREADS
循环)内获取信号量许可。这意味着在第一个线程启动后,主线程(正在运行循环)将持有信号量,并且在许可证可用之前不会释放它。这本质上会造成死锁,因为主线程正在等待只能由它刚刚启动的线程释放的许可,但该线程无法运行,因为主线程正在阻塞。
线程启动机制:要解决此问题,您应该从启动线程的循环中删除
semaphore.acquireUninterruptibly();
行。相反,管理线程本身内的信号量。每个线程应该在开始时获取信号量并在结束时释放它。这确保在任何给定时间,只有有限数量的线程(根据信号量的许可)正在执行其任务。
线程执行流程:通过每个线程内部的信号量获取/释放,主线程不会阻塞,可以继续创建和启动所有线程。信号量将确保只有有限数量的线程同时运行其任务。
处理“逃生舱口”:对于“逃生舱口”功能,您可能需要确保它不会干扰信号量机制。确保切换标志时,不会导致线程提前终止而不释放其信号量许可。
通过按所述调整信号量处理,您的线程应该能够并发运行,遵守信号量设置的限制,并且不会被主线程的循环阻塞。
尝试写详细的回复,希望有帮助:)