(Not logged on) | Log On
View  History

  

Using jsr166y.forkjoin Mappings and Predicates

8/18/2010 3:43 PM
You can subscribe to this wiki article using an RSS feed reader.

[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:

Singletons 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.

-- David Biesack