preface

Yesterday when I was nervous, I did 60 push-ups at a stretch. If you have the chance, you can also try, the tension is much relieved.

AsyncTask principles? How to understand AsyncTask? Will there be the thought that wants to express and oneself express wrong? Ineffective communication? That is sorry, GG, the bad place that this article says still hopes everybody points out.

This article extends from routine operations to FutureTask, which is associated with AsyncTask

directory

I. Asynchronous data synchronization

Problem: The timeliness of data acquisition in asynchronous threads is not a security of data manipulation in multithreading. For example, you initiate A request through A, how to get data through A, for example, A.et.

public class Demo { private static String result = "1"; public static void main(String[] args){ new Thread(new Runnable() { @Override public void run() { result = "2"; } }).start(); System.out.println(result); }}Copy the code

The problem with the code above is obvious to everyone. We want the result, but it is not satisfactory. We think of a thread as a network request, and result is the result it returns, so what do we need to do to get the correct return value later?

The while loop? Yes, but it’s not very user-friendly, CPU intensive, multiple thread operations have thread safety concerns, and so on

While (result==null){// here represents the data is empty wait delay sleep} result // get dataCopy the code

Is there a good way? You can add a callback with a return value, which is usually done in development. If the network request result is available, just call me back

System.out.println(System.currentTimeMillis()); new MyThread<String>(data -> { System.out.println(System.currentTimeMillis()); System.out.println(data); }).start(); interface Callback{ void callback(String data); } static class MyThread<T> extends Thread{ private Callback callback; private T result; public MyThread(Callback callback){ this.callback = callback; } @Override public void run() { try { Thread.sleep(5000); } catch (InterruptedException e) { e.printStackTrace(); } callback. Callback (" network request "); }}Copy the code

Yes, yes, but you need to set a callback, can I just get the data? Is there a way? There are

public static void main(String[] args){ RunnerThread thread = new RunnerThread<>(() -> { try { Thread.sleep(4000); } catch (InterruptedException e) { e.printStackTrace(); } return "network request "; }); thread.start(); System.out.println(System.currentTimeMillis()); System.out.println(thread.get()); System.out.println(System.currentTimeMillis()); } interface RunnerTask<T> { T run(); } static class RunnerThread<T> extends Thread{ private T result; private RunnerTask<T> task; private volatile boolean finished = false; public RunnerThread(RunnerTask<T> task){ this.task = task; } @Override public void run() { synchronized (this){ result = task.run(); finished = true; notifyAll(); }} public T get() {synchronized (this){ finished) { try { wait(); } catch (InterruptedException e) { e.printStackTrace(); } } return result; //output 1610550574299 Network request 1610550578299Copy the code

That is, the producer-consumer model can be used to solve such problems.

The final version is actually a shortened version of FutureTask. FutureTask is much more powerful, with internal CAS operations to verify states, including new, completed, cancelled, interrupted, and so on. Block the thread with locksupport. part, save data, and unblock the thread with locksupport. unpark

FutureTask<String> Future = new FutureTask<>(() -> {return "network request, download picture "; }); new Thread(future).start(); System.out.println(future.get()); // block here until your data comes backCopy the code

FutureTask set

FutureTask programming ideas

Basic use and source code analysis

FutureTask can block fetching results, so if you perform a time-consuming operation, can you block in multiple steps, for example

preCall(); Int result = futureTask.get(); if(result == 1){ doInBackground(); } void doInBackground(){int result = futuretask.get (); if(result == 2){ onPostExecute(); // Update progress}}Copy the code

And then I wonder if AsyncTask does something like this, does it do that? After all, I forgot about AsyncTask. Although behind slap slap face, but still need to continue to learn to understand.

Second, the AsyncTask

1,

Multithreaded task, time-consuming operation, the main thread will be notified of the result by the worker thread

2. Main methods

Internal thread pools

ThreadPoolExecutor threadPoolExecutor = new ThreadPoolExecutor(
       CORE_POOL_SIZE, MAXIMUM_POOL_SIZE, KEEP_ALIVE_SECONDS, TimeUnit.SECONDS,
       new SynchronousQueue<Runnable>(), sThreadFactory);
threadPoolExecutor.setRejectedExecutionHandler(sRunOnSerialPolicy);
Copy the code

The number of core threads is 1, the maximum number of threads is 20, the timeout time of non-core threads is 3 seconds, there is no blocking queue for elements, and the rejection policy will be executed using another thread pool

sBackupExecutorQueue = new LinkedBlockingQueue<Runnable>();
sBackupExecutor = new ThreadPoolExecutor(
                BACKUP_POOL_SIZE, BACKUP_POOL_SIZE, KEEP_ALIVE_SECONDS,
                TimeUnit.SECONDS, sBackupExecutorQueue, sThreadFactory);
 sBackupExecutor.allowCoreThreadTimeOut(true);
Copy the code

The number of core threads is 5, the maximum number of threads is 5, the timeout time of core threads is 3 seconds, the bounded blocking queue, the size is not specified.

4. Brief analysis of source code

The execution method calls the following method

public final AsyncTask<Params, Progress, Result> executeOnExecutor(Executor exec, Params... params) { if (mStatus ! = Status.PENDING) { switch (mStatus) { case RUNNING: throw new IllegalStateException("Cannot execute task:" + " the task is already running."); case FINISHED: throw new IllegalStateException("Cannot execute task:" + " the task has already been executed " + "(a task can be executed only once)"); } } mStatus = Status.RUNNING; onPreExecute(); mWorker.mParams = params; exec.execute(mFuture); return this; }Copy the code

When a new task is created, its state changes to RUNNING, FINISHED, and PENDING. Therefore, an AsyncTask can be invoked only once.

And then onPreExecute() is called before the task starts

Both mWorker and mFuture are initialized in AsyncTask constructors.

MFuture is also implemented through the following class

private static class SerialExecutor implements Executor { final ArrayDeque<Runnable> mTasks = new ArrayDeque<Runnable>(); Runnable mActive; public synchronized void execute(final Runnable r) { mTasks.offer(new Runnable() { public void run() { try { r.run(); } finally { scheduleNext(); }}}); if (mActive == null) { scheduleNext(); } } protected synchronized void scheduleNext() { if ((mActive = mTasks.poll()) ! = null) { THREAD_POOL_EXECUTOR.execute(mActive); }}}Copy the code

The thread pool executes the mFuture, so the focus shifts to the mFuture,

Let’s take a look at the internal creation logic of mWorker and mFuture

mWorker = new WorkerRunnable<Params, Result>() { public Result call() throws Exception { mTaskInvoked.set(true); Result result = null; try { Process.setThreadPriority(Process.THREAD_PRIORITY_BACKGROUND); //noinspection unchecked result = doInBackground(mParams); Binder.flushPendingCommands(); } catch (Throwable tr) { mCancelled.set(true); throw tr; } finally { postResult(result); } return result; }}; mFuture = new FutureTask<Result>(mWorker) { @Override protected void done() { try { postResultIfNotInvoked(get()); } catch (InterruptedException e) { android.util.Log.w(LOG_TAG, e); } catch (ExecutionException e) { throw new RuntimeException("An error occurred while executing doInBackground()", e.getCause()); } catch (CancellationException e) { postResultIfNotInvoked(null); }}};Copy the code

The thread pool executes the mFuture, which is an object of type FutureTask that implements the RunnableFuture interface, which inherits the Runnable class and Future, so calling the run method calls the Run method of RunnableFuture, Trigger mWorker’s call method.

So we call the doInBackground method, we do the time-consuming operation here, and finally, we do postResult(result)

    private Result postResult(Result result) {
        @SuppressWarnings("unchecked")
        Message message = getHandler().obtainMessage(MESSAGE_POST_RESULT,
                new AsyncTaskResult<Result>(this, result));
        message.sendToTarget();
        return result;
    }
Copy the code

Here, we switch to the main thread and call the AsyncTask method to terminate the task

      private void finish(Result result) {
        if (isCancelled()) {
            onCancelled(result);
        } else {
            onPostExecute(result);
        }
        mStatus = Status.FINISHED;
    }
Copy the code

When it’s done, the onPostExecute method is called.

The onProgressUpdate() method is triggered using the publishProgress method

protected final void publishProgress(Progress... values) { if (! isCancelled()) { getHandler().obtainMessage(MESSAGE_POST_PROGRESS, new AsyncTaskResult<Progress>(this, values)).sendToTarget(); } } case MESSAGE_POST_PROGRESS: result.mTask.onProgressUpdate(result.mData); // main thread break;Copy the code

The whole process is done in a simplified version, but you can also go into details about exceptions, such as callbacks to final results, guaranteed by AtomicBoolean

5. AsyncTask Handler

The AsyncTask class must be loaded in the main thread. The AsyncTask class must be loaded in the main thread. The AsyncTask class must be loaded in the main thread. Only its onPreExecute(), onProgressUpdate(), and onPostExecute() are executed on the thread calling it. For business purposes, AsyncTask is usually used in the main thread. It is not appropriate to say that AsyncTask must be used in the main thread.

         mHandler = callbackLooper == null || callbackLooper == Looper.getMainLooper()
            ? getMainHandler()
            : new Handler(callbackLooper);
Copy the code

6. Cancel method

AsyncTask cancel method call timing problem

     public final boolean cancel(boolean mayInterruptIfRunning) {
        mCancelled.set(true);
        return mFuture.cancel(mayInterruptIfRunning);
    }
Copy the code

Let’s look at the publishProgress method again

    protected final void publishProgress(Progress... values) {
        if (!isCancelled()) {
            getHandler().obtainMessage(MESSAGE_POST_PROGRESS,
                    new AsyncTaskResult<Progress>(this, values)).sendToTarget();
        }
    }
Copy the code

So, calling the cancel method, no matter how publishProgress is called, it calls the onCancel method, which is also the main thread or the called thread, right

     private void finish(Result result) {
        if (isCancelled()) {
            onCancelled(result);
        } else {
            onPostExecute(result);
        }
        mStatus = Status.FINISHED;
    }
Copy the code

7. Serial execution

public static final Executor SERIAL_EXECUTOR = new SerialExecutor(); private static class SerialExecutor implements Executor { final ArrayDeque<Runnable> mTasks = new ArrayDeque<Runnable>(); Runnable mActive; public synchronized void execute(final Runnable r) { mTasks.offer(new Runnable() { public void run() { try { r.run(); } finally { scheduleNext(); }}}); if (mActive == null) { scheduleNext(); } } protected synchronized void scheduleNext() { if ((mActive = mTasks.poll()) ! = null) { THREAD_POOL_EXECUTOR.execute(mActive); }}}Copy the code

The first AsyncTask is executed, and then it will fetch data from the mTask, causing the mActive to be empty. Therefore, it can be seen that AsyncTask is processed in serial. API version 29. Both serial and parallel tasks are based on performance considerations. Too many parallel tasks may lead to thread overflow or other abnormal problems. Serial tasks are not efficient but have low memory pollution.

Third, to reflect on

The details of AsyncTask are not analyzed in detail. On the whole, we already know how she does it. If you are interested, you can go into details by yourself.

Thread switch with Handler, asynchronous execution through the thread pool, thread safety with API atomic class, these technologies we all know, but mixed up with, this idea may be unexpected, want to advance, experience its design mode, packaging method routine, performance considerations, usually still have to ask why.

Read the source code in three steps

  • Understanding thoughts
    • Abandon the code and feel the author’s original intention and purpose in designing the framework
  • To grasp the design
    • Abandon the details and experience the code’s interfaces and abstract classes as well as the macro design
  • Understand the details
    • The code is gradually expanded based on the top-level abstract design