Thread Priority in Java
The program computes rowsPerThread as the image height divided by the thread count, using integer division.
Splitting One Image Among Several Threads
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 question this article answers is simple to state but easy to get wrong: how does the program decide which rows belong to which thread?
Throughout this article we will use one small case. The image is 10 rows tall and is divided among 3 threads. Every idea below is checked against that case. Before the explanation of each step, you will be asked to predict one result, so try to answer before reading on.
Computing rowsPerThread With Integer Division
The first step is to decide how many rows each thread should compute. The program stores this value in an integer called rowsPerThread, and it obtains the value by dividing the image height by the number of threads. Here, height is the vertical size of the image in pixels, and threadCount is the number of threads the user asked for.
Both height and threadCount are integers, so the division is integer division. Integer division drops any fractional part of the result. This matters because it means the division never produces a value that is 'between' whole rows. Whatever is left over has to be handled by a separate rule.
Dividing 10 rows among 3 threads
The image height is 10 and threadCount is 3. What value does rowsPerThread receive, and how many rows are left over?
- 1Divide the height by the thread count
10 divided by 3 is 3 with a fractional part of one third. Because both values are integers, the fraction is dropped.
- 2Read off the quotient
rowsPerThreadbecomes 3. Each thread is planned to handle 3 rows. - 3Compute the leftover rows
Three threads with 3 rows each cover 9 rows. The image has 10 rows, so 1 row is left over.
rowsPerThread is 3, and 1 row is left over. That leftover row is why the assignment needs a rule for the last thread.
Tracing the Row Range for Each Thread
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. Both values are inclusive, which means both the first row and the last row belong to that thread's work. The loop is where the division of labor becomes concrete.
startRow = rowsPerThread * i
endRow = rowsPerThread * (i + 1) - 1- 1i is the thread number, counting from 0 up to
threadCountminus 1. - 2
startRowmultiplies the block size by the thread number, so thread 0 starts at row 0, thread 1 starts after one full block, and so on. - 3
endRowadds one block to the start and subtracts 1, because the end row is inclusive and the next thread's block begins right after it. - 4The last thread is an exception. Its
endRowis set to height minus 1 instead, so that any leftover rows are not skipped.
Applying these formulas to the 10-row, 3-thread case gives the following ranges. Thread 0 starts at row 0 and ends at row 2. Thread 1 starts at row 3 and ends at row 5. Thread 2 is the last thread, so its start row is 3 times 2, which is 6, and its end row is set to height minus 1, which is 9. The figure below shows which thread owns each row.
Notice that row 9 is marked as the one that differs. Threads 0 and 1 each receive exactly rowsPerThread rows, but thread 2 receives four rows. That extra row is produced entirely by the final-thread rule, not by the general formulas.
The same loop can be traced one iteration at a time. The next figure shows how the start and end values change as i moves from 0 to 2. Pay attention to the last iteration, because that is where the exception applies.
Computing the range for the last thread
In the loop for the 10-row, 3-thread case, what startRow and endRow does the program assign when i equals 2?
- 1Compute the general start
startRowequalsrowsPerThreadtimes i, which is 3 times 2, giving 6. - 2Compute the general end
The general formula gives
rowsPerThreadtimes (i + 1) minus 1, which is 3 times 3 minus 1, giving 8. - 3Apply the last-thread rule
Because i equals
threadCountminus 1, the program replacesendRowwith height minus 1, which is 9.
Thread 2 is assigned rows 6 through 9, four rows in total.
Why the Last Thread Takes the Leftover Rows
The rule for the last thread exists because integer division can leave a remainder. Without the rule, the general formula would assign rows 6 through 8 to thread 2 and leave row 9 belonging to no thread. The program would then skip that row entirely. The exception guarantees that every row from 0 to height minus 1 is covered by exactly one thread.
What do you think happens?
Before reading the answer, predict: for the 10-row image split among 3 threads, what is the endRow of the last thread?
Reveal answer
9
The last thread's endRow is set to height minus 1, which is 10 minus 1, giving 9. The general formula would have produced 8, which is why the override is needed.
Where Thread Priority Fits In
Once a thread's rows are known, the program creates a Runner object with those two values and stores it in the workers array. The source then sets the new thread's priority to one less than the priority of the thread that is doing the creating, and only after that does it start the thread. The order for each thread is therefore create, set priority, start.
Priority is set during creation, but it does not change which rows a thread receives. The row boundaries are fixed by rowsPerThread and the loop variable i before any thread starts. When debugging a wrong result, you should therefore check the row arithmetic first, because priority only affects how the threads are set up and started.
The source also sets a shared flag named running to true before any thread starts, with a comment warning that the signal must be set before the threads begin. It initializes a counter named threadsCompleted to zero so that 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.
Mistakes That Break Row Assignment
For height 10 and threadCount 3: thread 0 gets rows 0-2, thread 1 gets rows 3-5, thread 2 gets rows 6-9.- Integer division on the first line drops the fraction, so
rowsPerThreadis 3. - The general
endRowformula subtracts 1 because the end row is inclusive. - The if block applies the last-thread rule that absorbs leftover rows.
Common mistake Omitting the last-thread override
What it looks likeLeaving endRow at the general formula gives thread 2 an endRow of 8 when the image is 10 rows tall.
Why it failsRow 9 then belongs to no thread, so the program skips it.
Do this insteadSet endRow to height minus 1 when i equals threadCount minus 1.
Common mistake Forgetting the minus 1 in the general endRow
What it looks likeUsing rowsPerThread times (i + 1) without subtracting 1 gives thread 0 an endRow of 3.
Why it failsThe end row is inclusive, so row 3 is counted by both thread 0 and thread 1.
Do this insteadSubtract 1 so that each block ends just before the next block starts.
Common mistake Assuming division rounds up
What it looks likeTreating 10 divided by 3 as 4 rows per thread.
Why it failsInteger division drops the fraction, so
rowsPerThreadis 3.Do this insteadUse the truncated value as the block size, and let the last thread absorb the remainder.
Common mistake Looking for the bug in priority settings
What it looks likeChanging the thread priority when the wrong rows are being computed.
Why it failsPriority does not change the row boundaries, which are set by
rowsPerThreadand i.Do this insteadTrace the start and end values for each i first, because that is where the wrong range first appears.
Practice: Partitioning a Different Image
An image is 14 rows tall and is divided among 4 threads. Compute rowsPerThread, then trace the startRow and endRow for every thread, including the last one. State which thread absorbs the leftover rows and how many rows it receives.
Hints
- Start with
rowsPerThreadequal to height divided bythreadCountusing integer division. - Use
startRowequal torowsPerThreadtimes i andendRowequal torowsPerThreadtimes (i + 1) minus 1 for every thread except the last. - Check your work by confirming that the ranges cover rows 0 through 13 with no gaps and no overlaps.
Key Takeaways
rowsPerThreadis computed as the image height divided by the thread count using integer division, which drops any fractional part.- For thread i,
startRowequalsrowsPerThreadtimes i, andendRowequalsrowsPerThreadtimes (i + 1) minus 1, because both rows are inclusive. - The last thread's
endRowis set to height minus 1, so leftover rows from the remainder are assigned rather than skipped. - Each worker is created with its row range, given a priority one less than the creating thread's priority, and then started, but priority does not change the row boundaries.
- When a result is wrong, trace the start and end values for each loop iteration, because the first incorrect range shows where the row-assignment bug begins.