In a previous post, I mentioned how to add a shutdown hook to your application. This is great for the case where you need to do some cleanup prior to exiting. The example below shows how to convert your class to work like a daemon on UNIX, or a service on Windows. I will not go into details on how to create a UNIX, or Windows service. I will provide a
StartupItem
example for Mac OS X, or a modern
inetd
plist for
launchd
.
In the example classes below, I create a value holder object, a
Callable<ValueHolder>
, a
Future<ValueHolder>
, an
ExecutorService
which uses a
SingleThreadExecutor
. The concurrency libraries are from java.util.concurrent which is based on the original work done by Doug Lea, et. al. These classes make doing
Thread
based programming much simpler.
Here are the relevant files for the application:
JavaDaemonExample.zip (NetBeans 7.0 project)
JavaDaemonStartupItem.zip (This is for OS X 10.4 or less. It will work on 10.5+, but is not preferred)
com.bluelotussoftware.examples.Daemon (This is the
inetd
plist for
launchd
10.5+ preferred)
Before we get to the code, the Mac OS X daemon controls must be modified to match your environment.
The
com.bluelotussoftware.examples.Daemon
plist file needs to modified to point to the jar file to execute. This is for Leopard, and Snow Leopard.
- To install it, place it in the
~/Library/LaunchAgents
directory.
- From a command prompt, type:
launchctl load -w /Users/jyeary/Library/LaunchAgents/JavaDaemonStartupItem/com.bluelotussoftware.examples.Daemon
This loads it.
- To execute from
launchctl
:
root:$ launctl
launchd% start com.bluelotussoftware.examples.Daemon
launchd% list
launchd% quit
root:$
Alternatively, you can use the
JavaDaemonStartupItem
.
- Modify the
JavaDaemonStartupItem
script to match your environment.
- Place the files in the
/Library/StartupItems
directory.
- Change permissions:
sudo chown -R root:wheel JavaDaemonStartupItem
- Execute the application using:
sudo /sbin/SystemStarter start "Java Daemon Example"
This is the main code Example:
Daemon.java
package com.bluelotussoftware.examples;
import java.io.IOException;
import java.util.Arrays;
import java.util.List;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;
import java.util.logging.FileHandler;
import java.util.logging.Handler;
import java.util.logging.Level;
import java.util.logging.Logger;
@author <jyeary@bluelotussoftware.com>
@version
public class Daemon {
private static final Logger logger = Logger.getLogger(Daemon.class.getName());
public Daemon() {
}
@param
public static void main(String[] args) {
try {
Handler handler = new FileHandler(Daemon.class.getName() + ".log");
logger.addHandler(handler);
} catch (IOException ex) {
ex.printStackTrace(System.err);
} catch (SecurityException ex) {
ex.printStackTrace(System.err);
}
logger.log(Level.INFO, "Closing streams");
closeDefaultStreams();
logger.log(Level.INFO, "Fetching ExecutorService");
final ExecutorService executorService = Executors.newSingleThreadExecutor();
logger.log(Level.INFO, "Adding Shutdown Hook");
Runtime.getRuntime().addShutdownHook(new Thread() {
@Override
public void run() {
shutdown(executorService);
}
});
logger.log(Level.INFO, "Submitting CallableDaemon");
Future<ValueHolder> future = executorService.submit(new CallableDaemon());
logger.log(Level.INFO, "Waiting for results...");
try {
StringBuilder sb = new StringBuilder();
sb.append("Results: ");
sb.append(Arrays.toString(future.get().getDates().toArray()));
logger.log(Level.INFO, sb.toString());
} catch (InterruptedException ex) {
logger.log(Level.SEVERE, null, ex);
} catch (ExecutionException ex) {
logger.log(Level.SEVERE, null, ex);
}
logger.log(Level.INFO, "Shutting down ExecutorService normally.");
executorService.shutdown();
}
@code @code @code
private static void closeDefaultStreams() {
System.out.close();
System.err.close();
try {
System.in.close();
} catch (IOException ex) {
logger.log(Level.WARNING, "Closing System.in() caused an Exception", ex);
}
}
@code
@param
private static void shutdown(ExecutorService executorService) {
logger.log(Level.INFO, "Executing shutdown hook");
List<Runnable> runnables = executorService.shutdownNow();
logger.log(Level.INFO, "The following items were awaiting execution when the daemon shutdown",
Arrays.toString(runnables.toArray(new Runnable[runnables.size()])));
}
}
This is the
Callable<ValueHolder>
:
CallableDaemon.java
package com.bluelotussoftware.examples;
import java.util.Date;
import java.util.concurrent.Callable;
<code></code>
<code></code> <code></code>
<code></code> <code></code>
@author <jyeary@bluelotussoftware.com>
@version
public class CallableDaemon implements Callable<ValueHolder> {
private int counter;
private int iterations = 12;
public CallableDaemon() {
}
@param
public CallableDaemon(int iterations) {
this.iterations = iterations;
}
@inheritDoc
@Override
public ValueHolder call() throws Exception {
ValueHolder valueHolder = new ValueHolder();
for (; counter < iterations; counter++) {
valueHolder.getDates().add(new Date());
synchronized (this) {
this.wait(5000);
}
}
return valueHolder;
}
}
ValueHolder
package com.bluelotussoftware.examples;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;
@author <jyeary@bluelotussoftware.com>
public class ValueHolder {
private List<Date> dates;
public ValueHolder() {
dates = new ArrayList<Date>();
}
@code <Date> <code></code>
<code></code>
@return@code <Date> <code></code>
public synchronized List<Date> getDates() {
return dates;
}
}