Как ведут себя переменные экземпляра, передаваемые между потоками с помощью AsyncTask?

Я понимаю, что небезопасно получать доступ к переменной общего экземпляра между несколькими потоками (если переменная не объявлена volatile и правильно synchronized). Я пытаюсь понять семантику передачи переменной общего экземпляра в фоновый поток с помощью Android AsyncTask,

Рассмотрим следующий код:

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; 
        }
    }
}

Что мы знаем о состоянии локальной переменной cv в doInBackground()? В частности,

  • Какие пары ключ-значение гарантированно будут в нем.

  • Какие пары ключ-значение могут быть в нем?

  • Что будет doInBackground() вернуть?

2 ответа

Решение

Если бы вы использовали базовый поток, поле члена не было бы синхронизировано, и видимость не будет гарантирована, как вы упомянули.

В случае использования AsyncTask это зависит от реализации инфраструктуры AsyncTask.

"one", 1 определенно будет там, потому что он помещается до создания потока.

Если мы проверим исходный код AsyncTask, мы сможем найти следующий комментарий:

* <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>

Так "three", 3 будет там, так как он был добавлен в onPreExecute,

Также это означает, что ContentValues contentValues; поле будет синхронизировано в точке doInBackground, поэтому метод вернет true.

Хотя я не думаю, что "two", 2 Элемент гарантированно будет там, так как этот код выполняется параллельно с асинхронным потоком. Может быть, но не обязательно. На это могут влиять как гонка, так и аспекты видимости.

Какие пары ключ-значение гарантированно будут в нем? Какие пары ключ-значение могут быть в нем?

  • в onPreExecute, contentValues будет иметь только одно значение, т.е. one=1, Не будет two=2, потому что ты .put("two", 2) после вызова выполнить.

  • в doInBackground, contentValues буду иметь three=3, two=2, one=1потому что вы добавили two=2 and three=3 или раньше onPreExecute,

Что возвратит doInBackground()?

doInBackground фон вернется trueпотому что очевидно cv == contentValues (тот же пример)

В качестве доказательства вышеприведенных утверждений я изменил ваш Example класс для печати некоторых сообщений на каждом этапе, чтобы вы могли понять состояние переменной экземпляра.

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)
        }
    }
}

Выход

 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
Другие вопросы по тегам