No, it isn't by default. That's why they let you override the run() method, which handles the flow of the mapper operations.
This is the source code, and default implementation for run:
public void run(Context context) throws IOException, InterruptedException {
setup(context);
while (context.nextKeyValue()) {
map(context.getCurrentKey(), context.getCurrentValue(), context);
}
cleanup(context);
}
And at the very end of the Mapper javaDoc, this phrase gives some light:
Applications may override the
run(org.apache.hadoop.mapreduce.Mapper.Context) method to exert
greater control on map processing e.g. multi-threaded Mappers etc.
So the idea is clear; You want thread-safety on multithread environment, that's cool...but you implement the logic. A very basic example of this could be (I synch all, because I don't know the details of each method/element, just for the example):
@Override
public void run(Context context) throws IOException, InterruptedException
{
synchronized(lock) //shared lock, class lock, etc..
{
setup(context);
while (context.nextKeyValue())
map(context.getCurrentKey(), context.getCurrentValue(), context);
cleanup(context);
}
}
Although there's no specific mention to multithreading with this method, something similar happens with map():
protected void map
Called once for each key/value pair in the input
split. Most applications should override this, but the default is the
identity function.
In resume, overriding these methods (at least one of them), should let you create a multithread Mapper extension.
So yes, it can be thread-safe, but it's your job to do so. The default implementation is not.
Update - MultithreadedMapper
Well, they do offer a multithreaded runnable mapper implementation:
Anyway, seems like the synchronization regarding the map/write operations are still your job; This just implements the mechanism to invoke run() from different pooled threads.
MultithreadedMapper
Multithreaded implementation for @link
org.apache.hadoop.mapreduce.Mapper. It can be used instead of the
default implementation, MapRunner, when the Map operation is not CPU
bound in order to improve throughput.
Mapper implementations using this MapRunnable must be thread-safe.
The Map-Reduce job has to be configured with the mapper to use via
setMapperClass(Job, Class) and the number of thread the thread-pool
can use with the getNumberOfThreads(JobContext) method. The default
value is 10 threads.
The key here seems to be the setMapperClass() method; There a custom thread-safe extension of Mapper should be passed as argument.
The interesting part is in main(), as defines the protocol to use the MultiMapper
public class MultithreadedZipContentLoader {
public static class ZipContentMapper extends Mapper<Text, Text, DocumentURI, Text>
{
private DocumentURI uri = new DocumentURI();
public void map(Text fileName, Text fileContent, Context context)
throws IOException, InterruptedException
{
uri.setUri(fileName.toString());
context.write(uri, fileContent);
}
}
public static void main(String[] args) throws Exception {
Configuration conf = new Configuration();
String[] otherArgs = new GenericOptionsParser(conf, args).getRemainingArgs();
if (otherArgs.length < 2) {
System.err.println("Usage: MultithreadedZipContentLoader configFile inputDir threadCount");
System.exit(2);
}
Job job = Job.getInstance(conf);
job.setJarByClass(MultithreadedZipContentLoader.class);
job.setInputFormatClass(ZipContentInputFormat.class);
job.setMapperClass(MultithreadedMapper.class);
MultithreadedMapper.setMapperClass(job, ZipContentMapper.class);
MultithreadedMapper.setNumberOfThreads(job, Integer.parseInt(args[2]));
job.setMapOutputKeyClass(DocumentURI.class);
job.setMapOutputValueClass(Text.class);
job.setOutputFormatClass(ContentOutputFormat.class);
ZipContentInputFormat.setInputPaths(job, new Path(otherArgs[1]));
conf = job.getConfiguration();
conf.addResource(otherArgs[0]);
System.exit(job.waitForCompletion(true) ? 0 : 1);
}
}
class ZipContentInputFormat extends FileInputFormat<Text, Text> {
@Override
protected boolean isSplitable(JobContext context, Path filename) {
return false;
}
@Override
public RecordReader<Text, Text> createRecordReader(InputSplit split,
TaskAttemptContext context) throws IOException, InterruptedException {
return new ZipContentReader();
}
}
...
The only thing that this example lacks is the synchronize part in the extended map method, as in this case seems there's no need to (no duplicate file uris, I guess).