使用 AsyncTask 在线程之间传递的实例变量的行为是什么?

What is the behavior of instance variables passed between threads with 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; 
        }
    }
}

我们对 doInBackground() 中局部变量 cv 的状态了解多少?具体来说,

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

  • onPreExecute中,contentValues将只有一个值,即one=1。不会有two=2,因为你调用execute后.put("two", 2)

  • doInBackground 中,contentValues 将具有 three=3, two=2, one=1,因为您在 onPreExecute 或之前添加了 two=2 and three=3

What will doInBackground() return?

doInBackground后台会returntrue,因为显然cv == contentValues(同一个实例)

作为对上述陈述的证明,我修改了你的Example class 以在每个阶段打印一些消息,以便你可以了解实例变量的状态。

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

如果您使用的是基本线程,则成员字段将不会同步,并且无法像您提到的那样保证可见性。

如果使用 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 点同步,因此该方法将 return 为真。

虽然我不认为 "two", 2 项一定会存在,因为该代码 运行 与异步线程并行。可能存在,但不一定。种族和能见度方面都会影响这一点。