import java.util.Date;
import java.util.List;
import java.util.ArrayList;
import java.text.SimpleDateFormat;
/**
* Attempt various strategies for making SimpleDateFormat safe, and report the
* runtime of each approach.
*
* @author Jesse Wilson
*/
public class SDFPerformance {
/** the pattern used when encoding dates */
private static final String SIMPLE_DATE_FORMAT_PATTERN = "EEE, d MMM yyyy HH:mm:ss Z";
/**
* Describe a strategy to make SimpleDateFormat safe.
*/
public interface DateFormatAccess {
public String format(Date date);
}
/**
* Create a new SimpleDateFormat on every invocation.
*/
public static class SharedDateFormatAccess implements DateFormatAccess {
private static SimpleDateFormat simpleDateFormat = new SimpleDateFormat(SIMPLE_DATE_FORMAT_PATTERN);
public String format(Date date) {
return simpleDateFormat.format(date);
}
}
/**
* Create a new SimpleDateFormat on every invocation.
*/
public static class NewInstanceDateFormatAccess implements DateFormatAccess {
public String format(Date date) {
return new SimpleDateFormat(SIMPLE_DATE_FORMAT_PATTERN).format(date);
}
}
/**
* Use the synchronized() keyword.
*/
public static class SynchronizedInstanceDateFormatAccess implements DateFormatAccess {
private SimpleDateFormat dateFormat = new SimpleDateFormat(SIMPLE_DATE_FORMAT_PATTERN);
public synchronized String format(Date date) {
return dateFormat.format(date);
}
}
/**
* Use a pool of SimpleDateFormat objects, implemented using an ArrayList.
*/
public static class ListPoolDateFormatAccess implements DateFormatAccess {
private List pool = new ArrayList(10);
public String format(Date date) {
SimpleDateFormat dateFormat;
synchronized(pool) {
int size = pool.size();
if(size == 0) dateFormat = new SimpleDateFormat(SIMPLE_DATE_FORMAT_PATTERN);
else dateFormat = (SimpleDateFormat)pool.remove(size - 1);
}
String result = dateFormat.format(date);
synchronized(pool) {
pool.add(dateFormat);
}
return result;
}
}
/**
* Use ThreadLocals for the implementation.
*/
public static class ThreadLocalDateFormatAccess implements DateFormatAccess {
ThreadLocal threadLocal = new DateFormatThreadLocal();
public String format(Date date) {
return threadLocal.get().format(date);
}
private static class DateFormatThreadLocal extends ThreadLocal {
protected SimpleDateFormat initialValue() {
return new SimpleDateFormat(SIMPLE_DATE_FORMAT_PATTERN);
}
}
}
/**
* Run a sequence of warm ups followed by a sequence of tests.
*/
public static void main(String[] args) {
System.out.println("WARM UP: 100 threads x 1000 executions");
System.out.println(test(new SharedDateFormatAccess(), 1000, 100));
System.out.println(test(new NewInstanceDateFormatAccess(), 1000, 100));
System.out.println(test(new SynchronizedInstanceDateFormatAccess(), 1000, 100));
System.out.println(test(new ListPoolDateFormatAccess(), 1000, 100));
System.out.println(test(new ThreadLocalDateFormatAccess(), 1000, 100));
System.out.println("RUN: 100 threads x 1000 executions");
System.out.println(test(new SharedDateFormatAccess(), 1000, 100));
System.out.println(test(new NewInstanceDateFormatAccess(), 1000, 100));
System.out.println(test(new SynchronizedInstanceDateFormatAccess(), 1000, 100));
System.out.println(test(new ListPoolDateFormatAccess(), 1000, 100));
System.out.println(test(new ThreadLocalDateFormatAccess(), 1000, 100));
}
/**
* Test a particular implemenation and return the result.
*/
private static TestResult test(final DateFormatAccess dateFormatAccess, final int roundsPerThread, final int threadCount) {
final TestResult testResult = new TestResult();
testResult.input = dateFormatAccess.getClass();
testResult.roundsPerThread = roundsPerThread;
testResult.threadCount = threadCount;
final Date dateA = new Date(System.currentTimeMillis());
final Date dateB = new Date(System.currentTimeMillis() + (1000L * 60 * 60 * 24 * 7));
final Date dateC = new Date(System.currentTimeMillis() + (1000L * 60 * 60 * 24 * 31));
final String expectedA = new SimpleDateFormat(SIMPLE_DATE_FORMAT_PATTERN).format(dateA);
final String expectedB = new SimpleDateFormat(SIMPLE_DATE_FORMAT_PATTERN).format(dateB);
final String expectedC = new SimpleDateFormat(SIMPLE_DATE_FORMAT_PATTERN).format(dateC);
final Thread[] threads = new Thread[threadCount];
for(int i = 0; i < threadCount; i++) {
final int previous = i - 1;
threads[i] = new Thread(new Runnable() {
public void run() {
try {
for(int j = 0; j < roundsPerThread; j++) {
String resultA = dateFormatAccess.format(dateA);
if(!expectedA.equals(resultA)) throw new IllegalStateException("Expected " + expectedA + " but found " + resultA);
String resultB = dateFormatAccess.format(dateB);
if(!expectedA.equals(resultA)) throw new IllegalStateException("Expected " + expectedB + " but found " + resultB);
String resultC = dateFormatAccess.format(dateC);
if(!expectedA.equals(resultA)) throw new IllegalStateException("Expected " + expectedC + " but found " + resultC);
Thread.sleep(1);
}
if(previous > 0) threads[previous].join();
} catch (RuntimeException e) {
testResult.exceptions.add(e);
} catch (InterruptedException e) {
System.exit(1);
}
}
});
}
long start = System.currentTimeMillis();
for(int i = 0; i < threadCount; i++) {
threads[i].start();
}
try {
threads[threadCount - 1].join();
} catch (InterruptedException e) {
e.printStackTrace();
}
long end = System.currentTimeMillis();
testResult.runtime = end - start;
return testResult;
}
/**
* The consequence of a test execution.
*/
private static class TestResult {
int threadCount;
int roundsPerThread;
Class input;
long runtime;
final List exceptions = new ArrayList();
public String toString() {
if(exceptions.isEmpty()) {
return "threads: " + threadCount + ", rounds: " + roundsPerThread + ", implementation " + input.getName() + ": time: " + runtime + "ms";
} else {
return "threads: " + threadCount + ", rounds: " + roundsPerThread + ", implementation " + input.getName() + " failed";
}
}
}
}