Concepts / Effective Thread Programming

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.

  • Programming

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

Definition

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.

Computation threadimageshared objectPainting codepaintComponent()Own object AOwn object Busesusesusesuses
Which threads hold a reference to the same image object, and which threads touch only their own separate objects?

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.

  1. 1
    List every thread in the program, including the main thread and any worker threads.
  2. 2
    For each thread, write down every object it reads or changes.
  3. 3
    Compare the lists and find each object that appears in two or more of them.
  4. 4
    Mark those objects as shared resources and check that every thread accesses them in a coordinated way.
Worked example

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?

  1. 1
    List the threads

    There are two threads: Thread A and Thread B.

  2. 2
    List 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.

  3. 3
    List 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.

  4. 4
    Find the overlap

    The score object appears in both lists. The local variables appear in only one list each, so they are not shared.

  5. 5
    Conclude

    The score object is the shared resource. It is the object that needs coordinated access.

Result

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.

java
Notes on the code
  • 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
Code walkthrough
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.
  1. 1
    Line 1 creates one Scoreboard object. Both threads will refer to this same object.
  2. 2
    Thread a is started with code that will add 10 to the score.
  3. 3
    synchronized (board) names board as the object the block is about.
  4. 4
    The statement inside the braces reads board.score, adds 10, and stores the result back.
  5. 5
    Thread 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.

A reads scoresees 0B reads scoresees 0A writes scorestores 10B writes scorestores 5final score5, expected 15thenthenthenresult
In what order do the threads' steps run on the shared score, and at which step does the final value differ from what the code seems to promise?

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.

MEDIUM

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 like

    Protecting a local variable that only one thread ever reads.

    Why it fails

    The source's test is overlap: an object is shared only when more than one thread uses it.

    Do this instead

    List 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 like

    Writing the synchronized statement around the thread rather than around the image or score object.

    Why it fails

    The source states that the synchronized statement is written around the shared object, not around a thread.

    Do this instead

    Put 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 like

    Checking only the final score when it is wrong.

    Why it fails

    The final value hides when the divergence began.

    Do this instead

    Trace the program step by step and find the first step where the shared object's state stops matching expectations.

Practice Finding the Shared Object

EASY

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.