Read text file filled with numbers and tranfer to an array
Bug

If the array length is not a multiplier of the number of threads, you'll miss the remainder! Quick example:

public static void printRange(int length, int n) {
    int range = length / n;
    for (int index = 0; index &lt; n; index ++){
        int start = index * range;
        int end = start + range;
        System.out.printf("[%s, %s)%n", start, end);
    }
}

// Output for printRange(10, 3):
[0, 3)
[3, 6)
[6, 9)

// Output for printRange(10, 6):
[0, 1)
[1, 2)
[2, 3)
[3, 4)
[4, 5)
[5, 6)


As you can see, you'll miss the 10th element for printRange(10, 3), and up to half the array for printRange(10, 6).

edit: A simplistic approach is to spin a new Thread to handle the remaining elements, but as you can see from the second example, you will end up a relatively high number of five elements for it, whereas the first five threads are only processing (and returning) one element. An alternative approach is to add an extra element per Thread so that you have more threads doing slightly more work, than one single thread doing most of the work.

Multi-threading

The preferred way of doing multi-threading since Java 5 is to use an ExecutorService to help you manage the lifecycle of threads. You should read up more on Oracle's tutorial to understand how they are used.

In addition, since you want each thread to compute and return a result for you, you should be looking at the Callable interface. Implementations override the call() method to return an appropriate result.

try-with-resources

If you are on Java 7 and above, you should use try-with-resources for efficient handling of the underlying I/O resource:

public static void main(String[] args) {
    try (Scanner scanner = new Scanner(System.in)) {
        // ...
    }
}


Hard-coding array length

int[] array = new int[100000];


What happens if your file has more than this number of elements? Actually, you also seem to be processing your file twice, once using the BufferedReader approach, and the second using a Scanner. Is this expected?

edit:

Getting the max value

Your current implementation forgets to actually compare the max of each partition with each other to arrive at the desired result.


  Is this meant to be more like an academic exercise on multi-threading? - myself


The reason why I'm asking is that while it looks like you're looking more at a map-reduce approach to your problem, it may not even be necessary in the first place. Processing 100,000 integers (based on your int[] declaration) or less in a single thread is relatively fast enough on any modern hardware capable of running the JVM. If you are however trying to compare billions of numbers and/or given a (strangely low) memory constraint, then that's when multi-threading will help. However, your question will be quite different at that stage.

Getting the max value (part 2)

From Java 8 onwards, there are two approaches of running tasks asynchronously:


ExecutorService to create Futures, and then getting the results from each of them.
Chaining CompletableFutures (Java 8 onwards).


The code below demonstrates both:

public class AsyncMax {

    private static final int MAX = 10;

    public static void main(String[] args) {
        System.out.println("Using ExecutorService:");
        ExecutorService service = Executors.newWorkStealingPool();
        List&lt;Future&lt;Integer&gt;&gt; esResults = IntStream.range(0, MAX).parallel()
                .mapToObj(i -&gt; service.submit(() -&gt; produce(i).get()))
                .collect(Collectors.toList());
        esResults.stream()
                .map(f -&gt; get(f))
                .max(Comparator.naturalOrder())
                .ifPresent(System.out::println);
        service.shutdown();
        System.out.println("Using CompletableFuture:");
        CompletableFuture&lt;List&lt;Integer&gt;&gt; cfResults = IntStream.range(0, MAX).parallel()
                .mapToObj(i -&gt; produce(i))
                .map(CompletableFuture::supplyAsync)
                .collect(allOf());
        cfResults.thenApply(results -&gt; results.stream().max(Comparator.naturalOrder()))
                .join()
                .ifPresent(System.out::println);
    }
}


For both approaches, IntStream.range(0, MAX).parallel() is used to parallelize the production of MAX integers, via the produce(int) method (shown below). The intermediary esResults and cfResults variables are created solely to highlight where the asynchronous tasks will 'end', to be followed by how the results are collected. One can certainly daisy-chain the method calls completely.


ExecutorService:


Start an ExecutorService implementation, the Java 8 newWorkStealingPool() is an example here.
submit() a Callable&lt;Integer&gt; that returns an Integer upon completion.
Collect the results to a List&lt;Future&lt;Integer&gt;&gt;, i.e. a List of Futures that return Integers.
Stream the List by get()-ting (method shown below) each resulting Integer.
From the Stream&lt;Integer&gt;, call max(Comparator.naturalOrder()) to get the maximum value, and then print it via ifPresent(Consumer).
Last, shutdown() the ExecutorService.

CompletableFuture:


Pass each Supplier&lt;Integer&gt; from produce(int) to CompletableFuture.supplyAsync(Supplier).
Collect each CompletableFuture&lt;Integer&gt; into a resulting CompletableFuture&lt;List&lt;Integer&gt;&gt; via a custom allOf() Collector (method shown below).
From that CompletableFuture, thenApply() it to a Function&lt;List&lt;Integer&gt;, Integer&gt; that derives the maximum value from the List&lt;Integer&gt;.
join() to get the result and print it via ifPresent(Consumer).



Supporting methods:


This produces the given int after sleep()-ing for some time:

private static Supplier&lt;Integer&gt; produce(int i) {
    return () -&gt; {
        try {
            Thread.sleep((int) ((1 + Math.random()) * (MAX - i) * 500));
        } catch (InterruptedException e) {
            Thread.currentThread().interrupt();
            throw new RuntimeException(e);
        }
        System.out.printf("Returning [%s] from Thread[%s]%n",
                i, Thread.currentThread().getName());
        return i;
    };
}

This is a Stream-friendly way of get()-ing from a Future, by catching checked Exceptions first:

private static &lt;T&gt; T get(Future&lt;T&gt; future) {
    try {
        return future.get();
    } catch (InterruptedException | ExecutionException e) {
        Thread.currentThread().interrupt();
        throw new RuntimeException(e);
    }
}

This is a Collector implementation for waiting on all CompletableFutures to finish, before returning the List of results (inspired by this article):

private static &lt;T&gt; Collector&lt;CompletableFuture&lt;T&gt;, ?,
        CompletableFuture&lt;List&lt;T&gt;&gt;&gt; allOf() {
    return Collector.of(ArrayList&lt;CompletableFuture&lt;T&gt;&gt;::new,
            (a, t) -&gt; a.add(t), (a, b) -&gt; { a.addAll(b); return a; },
            a -&gt; CompletableFuture.allOf(
                        a.toArray(new CompletableFuture[a.size()]))
                    .thenApply(v -&gt; a.stream().map(CompletableFuture::join)
                            .collect(Collectors.toList())));
}



Conclusion:

Instead of thinking solely on having to synchronize write access on a resulting array, both the ExecutorService or CompletableFuture approaches are more declarative, and IMO the latter more so that the former. This is achieved by letting us express how to generate our asynchronous tasks, followed by what needs to be done after gathering the results. :)