1

I understand that it's not safe to access a shared instance variable between multiple threads (unless the variable is declared volatile and properly synchronized). I'm trying to understand the semantics of passing a shared instance variable to a background thread using Android's AsyncTask.

Consider the following code:

public class Example {
    ContentValues contentValues;

    public void start() {
        contentValues = new ContentValues();
        contentValues.put("one", 1);
        new MyAsyncTask().execute(contentValues);
        contentValues.put("two", 2);
    }


    class MyAsyncTask extends AsyncTask<ContentValues, Void, Boolean> {
        @Override
        public void onPreExecute() {
            contentValues.put("three", 3);
        }

        @Override
        protected Boolean doInBackground(ContentValues... cvs) {
            ContentValues cv = cvs[0];
            return cv == contentValues; 
        }
    }
}

What do we know about the state of the local variable cv in doInBackground()? Specifically,

  • Which key-value pairs are guaranteed to be in it.

  • Which key-value pairs might be in it?

  • What will doInBackground() return?

dimo414
  • 47,227
  • 18
  • 148
  • 244
Ellen Spertus
  • 6,576
  • 9
  • 50
  • 101

2 Answers2

1

Which key-value pairs are guaranteed to be in it ? Which key-value pairs might be in it ?

  • in onPreExecute, the contentValues will have only one value, ie one=1. There won't be two=2, because you .put("two", 2) after calling execute.

  • in doInBackground, the contentValues will have three=3, two=2, one=1, because you added two=2 and three=3 on or before onPreExecute.

What will doInBackground() return?

doInBackground background will return true, because obviously cv == contentValues (same instance)

As a proof to above statements, I've modified your Example class to print some messages on each stage, so that you can understand the state of the instance variable.

Example.kt

class Example {

    internal lateinit var contentValues: ContentValues

    fun start() {
        contentValues = ContentValues()
        contentValues.put("one", 1)
        MyAsyncTask().execute(contentValues)
        contentValues.put("two", 2)
    }


    internal inner class MyAsyncTask : AsyncTask<ContentValues, Void, Boolean>() {
        public override fun onPreExecute() {
            Log.d("TAG", "ContentValue in onPreExecute is $contentValues")
            contentValues.put("three", 3)
        }

        override fun doInBackground(vararg cvs: ContentValues): Boolean? {
            val cv = cvs[0]
            Log.d("TAG", "ContentValue in doInBackground is $contentValues")
            return cv == contentValues
        }

        override fun onPostExecute(result: Boolean?) {
            Log.d("TAG", "Result is $result")
            Log.d("TAG", "ContentValue in onPostExecute is $contentValues")
            super.onPostExecute(result)
        }
    }
}

Output

 ContentValue in onPreExecute is one=1
 ContentValue in doInBackground is three=3 two=2 one=1
 Result is true
 ContentValue in onPostExecute is three=3 two=2 one=1
theapache64
  • 10,926
  • 9
  • 65
  • 108
  • Thank you, but my question isn't what happens if someone runs it but what is guaranteed by the language semantics. I know that the two threads' values of `contentValues` (accessed as an instance variable) are not guaranteed to be consistent due to per-thread caching. https://www.oreilly.com/library/view/learning-java-4th/9781449372477/ch09s03.html – Ellen Spertus May 07 '19 at 07:44
1

If you were using a basic thread the member field would not be synchronized and the visibility won't be guaranteed as you mention.

In case of using AsyncTask it depends on the implementation of the AsyncTask framework.

"one", 1 will definitely be there because it is put before the thread is created.

If we check the source code of AsyncTask we can find the following comment:

* <h2>Memory observability</h2>
 * <p>AsyncTask guarantees that all callback calls are synchronized in such a way that the following
 * operations are safe without explicit synchronizations.</p>
 * <ul>
 *     <li>Set member fields in the constructor or {@link #onPreExecute}, and refer to them
 *     in {@link #doInBackground}.
 *     <li>Set member fields in {@link #doInBackground}, and refer to them in
 *     {@link #onProgressUpdate} and {@link #onPostExecute}.
 * </ul>

So "three", 3 will be there since it was added in onPreExecute.

Also it means that the ContentValues contentValues; field will be synchronized at the point of doInBackground, so the method will return true.

Though I don't think that the "two", 2 item is guaranteed to be there, since that code is run in parallel with the async thread. Might be there, but not necessarily. Both race and visibility aspects can influence that.

TpoM6oH
  • 8,385
  • 3
  • 40
  • 72
  • Thank you. I don't think it's necessary to look at the source code, since the comment you cite is in the javadoc (https://developer.android.com/reference/android/os/AsyncTask), which I should have read more carefully. – Ellen Spertus May 11 '19 at 16:23