Effective Thread Programming
A shared resource is an object that more than one thread uses, such as the image object in the source's example.
When Two Threads Want the Same Object
A program that uses threads often has more than one piece of code running at the same time. Each of those pieces may reach for objects in the program. When two or more of them reach for the same object, that object is a shared resource. Shared resources are where most of the difficulty in threaded programming lives, because the program's behavior now depends on how the threads interleave with each other.
Consider the situation a background computation creates. One thread does work on an object while another thread uses that same object for something else. The question this article answers is how the program should handle that overlap so the work stays correct. Keep two questions in mind as you read: which object is being used by more than one thread, and what the code wraps around that object.
What Makes a Resource Shared
A shared resource is an object that more than one thread uses. The object is not shared because of its type or its name; it is shared because two or more threads hold a way to reach it and use it.
The source's running example is an image object. A computation thread does work that changes the image, while another thread uses the same image to draw it on the screen. Neither thread owns the image. The image is a single object in memory that both threads point to, so a change made by one thread is seen by any other thread that reads it afterward. Because of this, the source treats the image as a resource that needs synchronized access rather than as a private value that each thread keeps for itself.
The diagram shows the pattern. Both threads connect to the image, which is the shared resource. Each thread also has objects that only it uses. Only the image needs coordinated access, because only the image is reached from more than one thread.
Finding Shared Objects in a Program
Most of the work in synchronizing shared resources is noticing where sharing happens. The source gives a pattern you can apply to any multithreaded program. First, list the threads. Second, list the objects each thread uses. Third, look for any object that appears in more than one list. That object is the one that needs synchronized access.
- 1List every thread in the program, including the main thread and any worker threads.
- 2For each thread, write down every object it reads or changes.
- 3Compare the lists and find each object that appears in two or more of them.
- 4Mark those objects as shared resources and check that every thread accesses them in a coordinated way.
Finding the shared object in a scoreboard program
Two threads each add points to a player's score. Thread A adds points from one event, and Thread B adds points from another. Which object is shared?
- 1List the threads
There are two threads: Thread A and Thread B.
- 2List the objects for Thread A
Thread A uses the score object to add its points. It also uses a local variable for the event number, which only Thread A can see.
- 3List the objects for Thread B
Thread B uses the score object to add its points. It also uses its own local variable for its event number.
- 4Find the overlap
The score object appears in both lists. The local variables appear in only one list each, so they are not shared.
- 5Conclude
The score object is the shared resource. It is the object that needs coordinated access.
The score object is shared by both threads. The local event variables are not shared.
Reading the synchronized Statement
The source's form for protecting a shared resource is synchronized(object) followed by a code block. The object named in parentheses is the shared object. The braces wrap the code that uses that object. Read the form from left to right: the keyword says that the code which follows is synchronized, the parentheses name the object that the synchronization is about, and the block contains the statements that use that object.
- board is created once and is referenced by both threads
- synchronized (board) names the shared object in parentheses
- The block contains the code that reads and writes board.score
- Thread b uses the same form with the same named object
The example below is illustrative and follows the synchronized(object) { code block } form described in the source. It is not the source's own listing.- 1Line 1 creates one Scoreboard object. Both threads will refer to this same object.
- 2Thread a is started with code that will add 10 to the score.
- 3synchronized (board) names board as the object the block is about.
- 4The statement inside the braces reads board.score, adds 10, and stores the result back.
- 5Thread b does the same with 5, and it names the same board object in its own synchronized block.
Tracing Where the Result Diverges
When a multithreaded program gives a result you did not expect, the useful first question is where the result first departed from what you predicted. Work through the program one step at a time. For each step, note which thread is running and which shared object it touches. The first step where the object's state no longer matches your expectation is the place to look.
Here is a trace of the scoreboard without any coordination. Suppose the two threads interleave so that both read the score before either writes it. Each thread then works from the old value, and one thread's update overwrites the other's. The diagram traces one possible interleaving; the exact order depends on how the threads run.
Trace the steps to find the divergence. Thread A's update of 10 is lost because Thread B wrote its result after reading the same old value. The first step where the score stops matching your expectation is the write by Thread B, which stores 5 when the program's intent was to add 5 to a score that already held 10. Every step touched the same score object, so that object is where the divergence began.
Before reading further, predict this: if the synchronized blocks in the code example above are both entered for the same board object, what does that do to the interleaving? Would the final score still be 5? Write down your prediction and the step where you think the result would change.
Hints
- Ask which object each synchronized block names.
- Think about whether the two blocks can overlap in their work on board.score.
Mistakes Beginners Make with Shared Objects
Common mistake Treating every object in a threaded program as shared
What it looks likeProtecting a local variable that only one thread ever reads.
Why it failsThe source's test is overlap: an object is shared only when more than one thread uses it.
Do this insteadList each thread's objects first, then mark only the objects that appear in more than one list.
Common mistake Naming the thread instead of the shared object in the synchronized form
What it looks likeWriting the synchronized statement around the thread rather than around the image or score object.
Why it failsThe source states that the synchronized statement is written around the shared object, not around a thread.
Do this insteadPut the shared object in the parentheses, as in synchronized(board).
Common mistake Looking for the bug at the end of the program
What it looks likeChecking only the final score when it is wrong.
Why it failsThe final value hides when the divergence began.
Do this insteadTrace the program step by step and find the first step where the shared object's state stops matching expectations.
Practice Finding the Shared Object
A program has three threads. Thread 1 uses a cache object and a log object. Thread 2 uses the log object and a config object. Thread 3 uses only its own temporary buffer. Which objects are shared, and which synchronized form would you write for the shared log object?
Hints
- Make a list for each thread, then look for objects that appear in more than one list.
- The shared object goes inside the parentheses of the synchronized form.
Key Takeaways
- A shared resource is an object that more than one thread uses, such as the image object in the source's painting example.
- To find shared resources, list each thread's objects and look for any object that appears in more than one list.
- The form
synchronized(object){ code block } names the shared object in parentheses and wraps the code that uses it. - To locate a possible divergence, trace the program step by step and find the first step where the shared object's state stops matching what you expected.