我在初始化上下文时从Thread
开始ServletContextListener
,并在上下文被销毁时尝试停止它。课程是:
public enum BlinkLedTask {
INSTANCE;
private Logger logger = RpiLogger.getLogger(getClass());
private Task task;
private ExecutorService service;
private BlinkLedTask() {
}
public void run(String[] frequency) {
stop();
task = new Task(frequency);
service = Executors.newSingleThreadExecutor(RpiThreadFactory.INSTANCE);
service.execute(task);
}
public void stop() {
if(Objects.isNull(task) || Objects.isNull(service)) {
return;
}
try {
task.terminate();
service.shutdownNow();
} catch (Exception cause) {
logger.error(cause.getMessage(), cause);
}
}
private static class Task implements Runnable {
private volatile boolean running = true;
private String[] frequency;
private volatile Logger logger = RpiLogger.getLogger(getClass());
private Task(String[] frequency) {
this.frequency = frequency;
}
@Override
public void run() {
while(running && !Thread.interrupted()) {
try {
resetLed();
blinkLed();
} catch (Throwable cause) {
logger.error(cause.getMessage(), cause);
running = false;
try {
resetLed();
} catch (Throwable ignore) {
}
}
}
}
private void resetLed() throws Exception {
executeScript(Script.BLINK_LED_RESET);
}
private void blinkLed() throws Exception {
executeScript(Script.BLINK_LED, new String[]{frequency[0], frequency[1], frequency[2]});
}
private void executeScript(Script script, String... args) {
ScriptExecutor scriptExecutor = new ScriptExecutor(ScriptExecutor.BASH, script);
scriptExecutor.execute(true, args);
}
private void terminate() {
logger.info("Stopping - " + Thread.currentThread().getName());
running = false;
}
}
}
这是一个Singleton
,它运行一个shell脚本,直到它停止。可以从任何地方调用此类,因此在创建新的Thread
之前,如果当前正在执行shell脚本,我需要停止该线程。
出于测试目的,我在初始化上下文时执行了此类的run()
方法,并在销毁时调用stop()
。
我在删除代码run()
后重新部署了war文件,我希望stop()
会终止task
,但事实并非如此。
我也尝试了run()
和stop()
:
public void run(String[] frequency) {
stop();
task = new Task(frequency);
Thread thread = RpiThreadFactory.INSTANCE.newThread(task);
tasks.add(ImmutablePair.of(thread, task));
thread.start();
}
public void stop() {
for(ImmutablePair<Thread, Task> pair : tasks) {
try {
pair.right.terminate();
pair.left.join();
} catch (Exception ex) {
}
}
}
此处tasks
为private ArrayList<ImmutablePair<Thread, Task>> tasks = new ArrayList<ImmutablePair<Thread,Task>>();
。 ImmutablePair
属于commons-lang3。但是我在增强的for循环的迭代中收到了java.util.ConcurrentModificationException
。我不知道的原因。
当服务器关闭时,stop()
正在按预期工作。我正在使用Jetty。
RpiThreadFactory
:
import java.util.concurrent.ThreadFactory;
import java.util.concurrent.atomic.AtomicInteger;
import org.apache.log4j.Logger;
import com.edfx.rpi.app.utils.logger.RpiLogger;
public enum RpiThreadFactory implements ThreadFactory {
INSTANCE;
private final AtomicInteger poolNumber = new AtomicInteger(1);
private final Logger logger = RpiLogger.getLogger(getClass());
private final ThreadGroup threadGroup;
private final AtomicInteger threadNumber = new AtomicInteger(1);
private final String namePrefix;
private RpiThreadFactory() {
SecurityManager securityManager = System.getSecurityManager();
threadGroup = (securityManager != null) ? securityManager.getThreadGroup() : Thread.currentThread().getThreadGroup();
namePrefix = "RpiPool-" + poolNumber.getAndIncrement() + "-Thread-";
}
public Thread newThread(Runnable runnable) {
Thread thread = new Thread(threadGroup, runnable, namePrefix + threadNumber.getAndIncrement(), 0);
thread.setPriority(Thread.NORM_PRIORITY);
thread.setUncaughtExceptionHandler(new Thread.UncaughtExceptionHandler() {
public void uncaughtException(Thread thread, Throwable cause) {
logger.error(cause.getMessage(), cause);
}
});
return thread;
}
}
ScriptExecutor
:
import java.io.BufferedReader;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.Objects;
import org.apache.commons.lang3.StringUtils;
import org.apache.log4j.Logger;
import com.edfx.rpi.app.utils.logger.RpiLogger;
public class ScriptExecutor {
private static final Logger LOGGER = RpiLogger.getLogger(ScriptExecutor.class);
public static final String BASH = "/bin/bash";
private Script script;
private Process process;
private String output;
private int exitValue;
public ScriptExecutor(Script script) {
this.script = script;
}
public void execute(boolean destroyProcess, String... args) throws ScriptNotExistException {
if(!script.exists()) {
throw new ScriptNotExistException(script.getScriptName() + " does not exists.");
}
try {
List<String> commands = new ArrayList<>();
commands.add(BASH);
commands.add(script.getAbsoultePath());
if(Objects.nonNull(args)) {
commands.addAll(Arrays.asList(args));
}
StringBuilder builder = new StringBuilder("Executing script: ");
builder.append(script.getScriptName());
if(Objects.nonNull(args) && args.length > 0) {
builder.append(" with parameters: ");
builder.append(StringUtils.join(args, " "));
}
LOGGER.info(builder.toString());
ProcessBuilder processBuilder = new ProcessBuilder(commands.toArray(new String[commands.size()]));
process = processBuilder.start();
StringBuilder outputBuilder = new StringBuilder();
InputStream inputStream = process.getInputStream();
InputStreamReader inputStreamReader = new InputStreamReader(inputStream);
BufferedReader bufferedReader = new BufferedReader(inputStreamReader);
String line = StringUtils.EMPTY;
while ((line = bufferedReader.readLine()) != null) {
outputBuilder.append(line);
outputBuilder.append("\n");
}
process.waitFor();
exitValue = process.exitValue();
LOGGER.info("Process for: " + script.getScriptName() + " is executed. Exit value: " + exitValue);
if(destroyProcess) {
destroyProcess();
}
output = outputBuilder.toString();
} catch (Exception cause) {
throw new ScriptExecutionException(cause);
}
}
public String getOutput() {
return output;
}
public int getExitValue() {
return exitValue;
}
public void destroyProcess() {
if(Objects.nonNull(process)) {
LOGGER.info("Process for: " + script.getScriptName() + " is destroyed.");
process.destroy();
}
}
}
这是在Jetty Web容器中运行的Web应用程序。服务器安装在启用嵌入式硬件java中。这个硬件如何连接LED。应用程序接受外部请求,可以是REST并启动LED。因此LED可以根据任何请求开始闪烁;但它一次只能提供一个请求。
这就是为什么我有stop
停止以前运行的进程,如果有的话。 stop
适用于正常情况。
但是我看到,当LED闪烁并且我在没有停止服务器的情况下进行部署时,正在运行的线程不会停止。如果我停止服务器并进行部署并再次启动,则此时正在运行的线程将被终止。
线程在while
中循环,并对本机执行Process
。这个Process
是一次性工作,所以这个Process
并没有让线程被杀死。
为了重现我所做的问题,我在初始化上下文时创建了线程,并在它被销毁时尝试杀死它。现在,如果我在contextDestroyed
中写一些东西,我可以看到它们被执行了。
我不明白为什么在重新部署时不会停止服务器杀死线程。
答案 0 :(得分:3)
您应该在processBuilder.start()返回的Process实例上调用process.destroy()。实际上你在调用BlinkLedTask.terminate()时所做的只是设置一些标志。你应该在这一点上调用process.destroy()。
下面我将举例说明如何重写它。它不涉及你的ScriptExecutor类(当然你可以在那里移动逻辑并在调用blinkLed()时将进程实例返回到BlinkLedTask)。
这里的主要区别在于我在字段blinkLedProcess中保持对Process实例的引用,并且当调用terminate()时,我直接调用process.destroy()来销毁该进程。
您写道“当服务器关闭时,stop()正在按预期工作。我正在使用Jetty。”确实是的。这是因为通过调用processBuilder.start();
,您可以创建主要码头流程的子流程。当你杀死码头时,它的所有子潜艇都被杀死了。如果不杀死jetty,则需要通过调用destroy()方法手动终止子进程。
应该是这样的:
public enum BlinkLedTask {
(...)
private Process resetLedProcess;
private Process blinkLedProcess;
(...)
private void blinkLed() throws Exception {
String[] args = new String[] { frequency[0], frequency[1], frequency[2] };
List<String> commands = new ArrayList<>();
//commands.add(BASH);
commands.add(script.getAbsoultePath());
if (Objects.nonNull(args)) {
commands.addAll(Arrays.asList(args));
}
StringBuilder builder = new StringBuilder("Executing script: ");
builder.append(script.getAbsoultePath());
if (Objects.nonNull(args) && (args.length > 0)) {
builder.append(" with parameters: ");
builder.append(StringUtils.join(args, " "));
}
ProcessBuilder processBuilder = new ProcessBuilder(commands.toArray(new String[commands.size()]));
blinkLedProcess = processBuilder.start();
StringBuilder outputBuilder = new StringBuilder();
InputStream inputStream = blinkLedProcess.getInputStream();
InputStreamReader inputStreamReader = new InputStreamReader(inputStream);
BufferedReader bufferedReader = new BufferedReader(inputStreamReader);
String line = StringUtils.EMPTY;
while ((line = bufferedReader.readLine()) != null) {
outputBuilder.append(line);
outputBuilder.append("\n");
}
blinkLedProcess.waitFor();
int exitValue = blinkLedProcess.exitValue();
System.out.println(
"Process for: " + Script.BLINK_LED.getAbsoultePath() + " is executed. Exit value: " + exitValue);
}
(...)
private void terminate() {
System.out.println("Stopping - " + Thread.currentThread().getName());
running = false;
if (resetLedProcess != null) {
resetLedProcess.destroy();
System.out.println("Destroyed reset process");
}
if (blinkLedProcess != null) {
blinkLedProcess.destroy();
System.out.println("Destroyed blink process");
}
}
(...)
}
答案 1 :(得分:0)
首先,您应该在致电awaitTermination
之后使用shutdownNow
等待。
shutdownNow
会打断你的线程。你确定ScriptExecutor
没有压制中断吗?
这实际上可能是造成这种情况的原因。
使用SchedulerService
执行此操作似乎也很多余,因为您只使用一个线程。
您可以启动一个设置为守护程序线程的新线程(请参阅What is Daemon thread in Java?),当程序退出时它将自动关闭。
答案 2 :(得分:0)
你到底想要达到什么目的?如果它是一个在你的web应用程序生命周期内运行的单个线程,那么我只想用这样的线程编写你自己的上下文监听器......
public class MyContextListener implements ServletContextListener {
Thread myThread;
Task myTask;
@Override
public void contextInitialized(ServletContextEvent sce) {
myTask = new Task();
myThread = new Thread(myTask);
myThread.start();
}
@Override
public void contextDestroyed(ServletContextEvent sce) {
myTask.terminate();
myThread.interrupt();
myThread.join();
}
}
我想知道你想要实现的目标。你只想要一个运行一系列脚本的线程吗?或者您希望在将来的某个时候有多线程应用程序?
servlet上下文的任何原因?你可以运行这个直接的java应用程序吗?