Concepts / Thread Creation and Management

Thread Creation and Management

The program computes rowsPerThread as the image height divided by the thread count, using integer division.

  • Programming

A Picture Too Big for One Thread

Imagine a program that computes the pixels of a large image. A single thread would have to visit every row by itself. The program in this lesson takes a different approach: when the user clicks the Start button, it creates and starts a specified number of threads, and it gives each thread its own segment of the image to compute. The central question is simple to state but easy to get wrong: how does the program decide which rows belong to which thread?

Keep one small case in mind throughout this article. The image is 10 rows tall, and it is divided among 3 threads. Every idea below is checked against this case.

What do you think happens?

The image is 10 rows tall and the user asked for 3 threads. How many rows does each thread get, as the program stores it in rowsPerThread?

  • 3

  • 3.33

  • 4

  • 10

Reveal answer
Answer:

3

The program divides the image height by the thread count using integer division. Because both values are integers, the fractional part is dropped: 10 divided by 3 gives 3, with 1 row left over. Making the guess first matters because the leftover row is the source of the tricky part of this topic.

Tracing Row Ranges for a 10-Row Image

Each thread receives an inclusive band of rows. Inclusive means both the start row and the end row belong to that thread's work. Before looking at the formulas, look at the result the program is aiming for. With 10 rows and 3 threads, the rows should be split into three contiguous blocks that together cover every row from 0 to 9 exactly once.

Thread 0rows 0 to 2Thread 1rows 3 to 5Thread 2 (last)rows 6 to 9next blocknext block
For a 10-row image split among 3 threads, which contiguous block of rows does each thread own, and where does each block start and end?
Worked example

Assigning rows for a 10-row image and 3 threads

Image height is 10, threadCount is 3. Find rowsPerThread and the start row and end row for each thread, with the last thread receiving any leftover rows.

  1. 1
    Compute rowsPerThread

    Divide height by threadCount using integer division: 10 / 3 = 3, with the fraction dropped.

  2. 2
    Thread 0

    startRow = 3 times 0 = 0. endRow = 3 times (0 + 1) minus 1 = 2. Thread 0 owns rows 0 through 2.

  3. 3
    Thread 1

    startRow = 3 times 1 = 3. endRow = 3 times (1 + 1) minus 1 = 5. Thread 1 owns rows 3 through 5.

  4. 4
    Thread 2 (last)

    startRow = 3 times 2 = 6. The normal formula would give endRow = 3 times 3 minus 1 = 8, but the last thread's endRow is set to height minus 1 = 9, so row 9 is included.

  5. 5
    Check coverage

    The ranges 0 to 2, 3 to 5, and 6 to 9 together cover all 10 rows with no gaps and no overlaps.

Result

Thread 0: rows 0 to 2. Thread 1: rows 3 to 5. Thread 2: rows 6 to 9. All 10 rows are assigned.

Why Integer Division Leaves a Row Behind

The first step is to decide how many rows each thread should compute. The program stores this in an integer called rowsPerThread. Here, height is the vertical size of the image in pixels, and threadCount is the number of threads the user asked for. Both values are integers, so the division is integer division, and any fractional part is dropped.

Pattern
rowsPerThread = height / threadCount   (integer division: the fractional part is discarded)
Image height 10rows in the imagerowsPerThread 310 divided by 3Rows assigned 93 times 3Thread count 3threads requestedLeftover row 110 minus 9integer divisiondivisortimes 3 threadsremainder
When height is 10 and threadCount is 3, how many rows does the per-thread value account for, and how does that total compare with the real image height?

For our example, 10 divided by 3 gives 3 rows per thread, with 1 row left over. Multiplying 3 by 3 threads accounts for only 9 rows. The per-thread value alone cannot cover the whole image, so the assignment needs a rule for the last thread.

Giving the Last Thread the Leftover Rows

For every thread except the last, the program uses two formulas. The start row is rowsPerThread times i, and the end row is rowsPerThread times (i + 1) minus 1, where i is the thread number. These formulas give each thread a block of exactly rowsPerThread rows.

Pattern
startRow = rowsPerThread * i ;  endRow = rowsPerThread * (i + 1) - 1   (for all threads except the last)

Each thread's range is inclusive at both ends, so the ranges fit together with no gap and no overlap. Thread 0 ends at row 2 and thread 1 starts at row 3. Thread 1 ends at row 5 and thread 2 starts at row 6. The only range that changes its shape is the last one, which stretches to row 9 to absorb the leftover row.

Following the Loop Pass by Pass

The program loops over the thread numbers, from 0 up to threadCount minus 1. In each pass it calculates a first row and a last row for that thread. Once those rows are known, the program creates a Runner object with the two values and stores it in the workers array. It then sets the new thread's priority to one less than the priority of the thread doing the creating, and only after that does it start the thread. The sequence for each thread is therefore create, set priority, start.

i = 0start 0, end 2i = 1start 3, end 5i = 2 (last)start 6, end 9i increases by 1i increases by 1
As the loop advances from thread 0 to the last thread, how do the start and end values change on each iteration?
  1. 1
    Start of pass i = 0: compute startRow = 0 and endRow = 2, create the Runner with those rows, set its priority, and start it.
  2. 2
    Start of pass i = 1: compute startRow = 3 and endRow = 5, create the Runner, set its priority, and start it.
  3. 3
    Start of pass i = 2: compute startRow = 6 and endRow = 9 because this is the last thread, create the Runner, set its priority, and start it.
  4. 4
    After the loop: every row from 0 to 9 has been handed to exactly one thread.

Before the loop starts, the source sets a shared flag named running to true, with a comment warning that the signal must be set before the threads begin. It also initializes a counter, threadsCompleted, to zero so the program can later record how many threads have finished. The source excerpt does not show the Runner class itself, so this article treats its constructor only as taking a start row and an end row.

Where the Row Assignment Bug First Appears

When the result does not match expectations, the bug almost always traces back to a small set of steps in the assignment calculation. The list below names the most common ones, each tied to the 10-row, 3-thread case.

  • Common mistake

    Assuming rowsPerThread is exactly height divided by threadCount

    What it looks like

    Treating 10 divided by 3 as 3.33 rows per thread.

    Why it fails

    Both height and threadCount are integers, so the division is integer division and the fractional part is dropped. rowsPerThread is 3.

    Do this instead

    Expect rowsPerThread to be 3 and account for the leftover row separately.

  • Common mistake

    Giving every thread the general end-row formula, including the last thread

    What it looks like

    Thread 2 gets endRow = 3 times 3 minus 1 = 8.

    Why it fails

    The last thread's endRow is supposed to be height minus 1, which is 9. Using 8 skips row 9, so the rows assigned total only 9 of the 10.

    Do this instead

    Set the last thread's endRow to height minus 1 instead of the general formula.

  • Common mistake

    Dropping the minus 1 in the end-row formula

    What it looks like

    Thread 0 gets endRow = 3 instead of 2.

    Why it fails

    The end row is inclusive. Without the minus 1, thread 0 also owns row 3, which thread 1 starts with, so rows overlap.

    Do this instead

    Use endRow = rowsPerThread times (i + 1) minus 1 for every thread except the last.

  • Common mistake

    Checking only the first and last thread and not the boundaries between them, which can hide overlaps or gaps in the middle of the sequence.

Predicting a Different Split

MEDIUM

An image is 7 rows tall and the user asks for 3 threads. Compute rowsPerThread, then give the start row and end row for each of the three threads, making sure the last thread covers every leftover row. Check that your three ranges together cover rows 0 through 6 exactly once.

Hints
  • Start with integer division: 7 divided by 3 drops the fraction.
  • Use the general formulas for threads 0 and 1, then set the last thread's endRow to height minus 1.
  • Your last range should end at row 6.

Key Takeaways

  • The program computes rowsPerThread as height divided by threadCount using integer division, so any fractional part of the division is dropped.
  • For threads other than the last, startRow = rowsPerThread * i and endRow = rowsPerThread * (i + 1) - 1, giving each thread an inclusive band of rows.
  • The last thread's endRow is set to height minus 1, so leftover rows are not skipped; for 10 rows and 3 threads, the rows are 0 to 2, 3 to 5, and 6 to 9.
  • The row-assignment bug first appears at the rowsPerThread step, where the remainder is silently lost, and becomes visible when the last thread's range or the assigned total is checked.