|
|
[Up to jsr166y.forkjoin Examples]
A best practice in concurrent programming (see Java Concurrency in Practice - JCIP) is to use immutable objects. Especially when using mappings, reducers/combiners and filters/predicates in Fork/Join, the operations will perform best if they are immutable. This is because they do not need to use synchronization or atomics to guard any mutable state - there is no mutable state.
For example, if you need mappers to compute the absolute value and square root of a double value, you can hide the implementation classes and expose the predicate instances as shown in the sample Mappings class below:
import jsr166y.forkjoin.Ops.DoubleOp;
public final class Mappings{ // first draft; this version is improved below
private Mappings() { // prevent instantiation; use only static methods
}
public static DoubleOp abs() {
return new AbsOp();
}
public static DoubleOp sqrt() {
return new SqrtOp();
}
private static class AbsOp implements DoubleOp {
@Override
public double op(final double a) {
return Math.abs(a);
}
}
private static class SqrtOp implements DoubleOp {
@Override
public double op(final double a) {
return Math.sqrt(a);
}
}
}
However, you can also reduce an application's memory footprint by using Singletons for many operations - especially the immutable ones.
import jsr166y.forkjoin.Ops.DoubleOp;
public final class Mappings { // Improved version uses Singletons
private Mappings() { // prevent instantiation; use only static methods
}
public static DoubleOp abs() {
return AbsOp.INSTANCE;
}
public static DoubleOp sqrt() {
return SqrtOp.INSTANCE;
}
private static class AbsOp implements DoubleOp {
static DoubleOp INSTANCE = new AbsOp();
@Override
public double op(final double a) {
return Math.abs(a);
}
}
private static class SqrtOp implements DoubleOp {
static DoubleOp INSTANCE = new SqrtOp();
@Override
public double op(final double a) {
return Math.sqrt(a);
}
}
}
CommonOps does something like this with many of the commonly used reducers/combiners:
doubleAdder(), intAdder(), longAdder() identityPredicate()isNullPredicate(), isNonNullPredicate() naturalDoubleMinReducer(), naturalLongMinReducer(), etcSingletons are not always appropriate. For example, sometimes the Predicate or Op is immutable but requires a constructor parameter:
public DoubleOp adder(double addend) {
return new Adder(added);
}static class Adder implements DoubleOp {
private final double addend;
Adder(final double addend) {
this.addend = addend;
}
@Override
public double op(final double a) {
return a + addend;
}
}
This class cannot be implemented with a Singleton since different instances must be created for each addend that the application needs:
ParallelDoubleArray a = ...;
// to add x to all values in the PA:
a.replaceWithMapping(Mappings.adder(x));
Where singletons are not appropriate, you can still use factory methods so that you can control how (or how many) instances are created.
For example, consider how java.lang.Long.valueOf(long l) is the preferred interface for obtaining a Long instance; it allows reuse of some common Long instances and is used internally by Java 5 autoboxing. A similar strategy can be employed for mappers, reducers, combiners, or predicates. One way to simplify implementation of such factory methods is to use a Memoizer as described in JCIP.