Thread Creation and Management
The program computes rowsPerThread as the image height divided by the thread count, using integer division.
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?
Reveal 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.
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.
- 1Compute rowsPerThread
Divide height by
threadCountusing integer division: 10 / 3 = 3, with the fraction dropped. - 2Thread 0
startRow= 3 times 0 = 0.endRow= 3 times (0 + 1) minus 1 = 2. Thread 0 owns rows 0 through 2. - 3Thread 1
startRow= 3 times 1 = 3.endRow= 3 times (1 + 1) minus 1 = 5. Thread 1 owns rows 3 through 5. - 4Thread 2 (last)
startRow= 3 times 2 = 6. The normal formula would giveendRow= 3 times 3 minus 1 = 8, but the last thread'sendRowis set to height minus 1 = 9, so row 9 is included. - 5Check coverage
The ranges 0 to 2, 3 to 5, and 6 to 9 together cover all 10 rows with no gaps and no overlaps.
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.
rowsPerThread = height / threadCount (integer division: the fractional part is discarded)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.
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.
- 1Start of pass i = 0: compute
startRow= 0 andendRow= 2, create the Runner with those rows, set its priority, and start it. - 2Start of pass i = 1: compute
startRow= 3 andendRow= 5, create the Runner, set its priority, and start it. - 3Start of pass i = 2: compute
startRow= 6 andendRow= 9 because this is the last thread, create the Runner, set its priority, and start it. - 4After 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 likeTreating 10 divided by 3 as 3.33 rows per thread.
Why it failsBoth height and
threadCountare integers, so the division is integer division and the fractional part is dropped.rowsPerThreadis 3.Do this insteadExpect 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 likeThread 2 gets endRow = 3 times 3 minus 1 = 8.
Why it failsThe last thread's
endRowis 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 insteadSet 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 likeThread 0 gets endRow = 3 instead of 2.
Why it failsThe end row is inclusive. Without the minus 1, thread 0 also owns row 3, which thread 1 starts with, so rows overlap.
Do this insteadUse 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
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
endRowto height minus 1. - Your last range should end at row 6.
Key Takeaways
- The program computes
rowsPerThreadas height divided bythreadCountusing integer division, so any fractional part of the division is dropped. - For threads other than the last,
startRow=rowsPerThread* i andendRow=rowsPerThread* (i + 1) - 1, giving each thread an inclusive band of rows. - The last thread's
endRowis 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
rowsPerThreadstep, where the remainder is silently lost, and becomes visible when the last thread's range or the assigned total is checked.