[Android Interview] 2023 Latest Interview Topic 10: Java Concurrent Programming (5)

21 Thread pool management thread principle

What is this question trying to investigate?

Do you understand the theoretical knowledge related to the thread pool?

Knowledge points of inspection

  1. The basic concepts in threads, the life cycle of threads
  2. The principle of thread pool
  3. The characteristics of several common thread pools and their respective application scenarios

How should candidates answer

In an application, we need to use threads multiple times, which means we need to create and destroy threads multiple times. The process of creating and destroying threads is bound to consume memory. In Java, memory resources are extremely precious, so we proposed the concept of thread pool. The advantage of the thread pool is that it can manage threads conveniently and reduce memory consumption.

Creation of thread pool

public ThreadPoolExecutor(int corePoolSize,  
                              int maximumPoolSize,  
                              long keepAliveTime,  
                              TimeUnit unit,  
                              BlockingQueue<Runnable> workQueue,  
                              ThreadFactory threadFactory,  
                              RejectedExecutionHandler handler)
  1. corePoolSize: The number of core threads. By default, the thread pool will always maintain corePoolSize threads so that these threads will not be recycled.
  2. maximumPoolSize: the maximum number of threads in the thread pool;
  3. keepAliveTime: The longest idle time of the thread, if the thread is idle for more than this time, it will be recycled;
  4. util: idle time unit;
  5. workQueue: Waiting queue. When the number of tasks executed in the thread pool exceeds the number of core threads, the newly submitted tasks will join this queue and wait for execution;
  6. threadFactory: thread factory that creates threads
  7. handler: rejection strategy, how to deal with the tasks that continue to be added after the task is full.

Execution process of thread pool

insert image description here

In the newly created thread pool, there are no threads in the default by default. Of course, methods can be used prestartAllCoreThreadsto advance the core threads of corePoolSize.

When submitting tasks to the thread pool, first determine whether the number of threads being executed in the thread pool has reached the number of core threads:

  • Not reached: Create a new thread to perform the task
  • Reached: add the task to the task queue

When adding a task to the task queue, the addition may be successful or the addition may fail:

  • Success: Wait for other threads to finish executing, and will automatically get tasks from the queue to continue executing;
  • Failed: Determine whether the current number of threads in the thread pool reaches the maximum number of threads

If adding a queue fails, there may be two results when judging whether the maximum number of threads is reached:

  • Reach: callback RejectedExecutionHandlerrejection policy, JDK provides four rejection policy processing classes: AbortPolicy (throws an exception, the default), DiscardPolicy (discards the task directly), DiscardOldestPolicy (discards the oldest task in the queue, and continues to submit the current task to the thread pool), CallerRunsPolicy (handed over to the thread where the thread pool call is located for processing)
  • Not reached: Create a new thread to perform the task

core thread

By default, the thread pool will always maintain the core thread so that it will not be recycled.

Core threads that can be allowCoreThreadTimeOut(true)set will also be idled and recycled.

The thread pool runs threads to perform tasks:

while (task != null || (task = getTask()) != null) {
    
    
    //......
    task.run();
}

Among them, getTasktasks to be executed will be obtained from the queue and executed continuously. If there is no task to be executed in the current queue, the non-core thread will wait for the specified keepAliveTime to exit the thread normally, and the core thread will takeblock through the task queue method to ensure its continuous operation.

 Runnable r = timed ?
					//非核心线程
                    workQueue.poll(keepAliveTime, TimeUnit.NANOSECONDS) :
					//核心线程
                    workQueue.take();

22 There are several ways to implement the thread pool. What are the seven parameters of the thread pool? (Meituan)

What is this question trying to investigate?

Do you know the seven parameters of the thread pool and use them in real scenarios? Are you familiar with the seven parameters of the thread pool?

Knowledge points of inspection

What are the concepts of the seven parameters of the thread pool used in the project and basic knowledge

How should candidates answer

Seven parameters

  1. corePoolSize

    The number of resident core threads in the thread pool

  2. maximumPoolSize

    The thread pool can hold the maximum number of threads that can be executed at the same time, this value must be greater than or equal to 1

  3. keepAliveTime

    Time to live for idle threads.

  4. unit

    Unit of keepAliveTime

  5. workQueue

    Task queue, tasks that have been submitted but not yet executed

  6. threadFactory

    Represents the thread factory that generates worker threads in the thread pool, which is generally used to create threads by default

  7. handler:

    Rejection strategy, indicating how to deal with when the queue is full and the worker thread is greater than or equal to the maximum number of threads in the thread pool (maximumPoolSize)

Method to realize

Using the thread pool that comes with the JDK can be done by creating a ThreadPoolExecutor thread pool object, or by using the static methods defined in Executors. Among them, the thread pool created by the Executors static method mainly has the following types:

1.newSingleThreadExecutor

Create a thread pool with only one thread, and the survival time of the thread is unlimited; when the thread is busy, it will enter the blocking queue for new tasks (unbounded blocking queue)

​ Applicable: Scenarios where one task is executed one task at a time

2.newCachedThreadPool

​ When a new task arrives, it will be inserted into the SynchronousQueue. Since the SynchronousQueue is a synchronous queue, it will look for available threads in the pool to execute. If there are available threads, execute them. If there are no available threads, create a thread to execute the task; If the idle time of the thread in the pool exceeds the specified size, the thread will be destroyed.

​ Applicable: Execute many short-term asynchronous scenarios

3.newFixedThreadPool

​ Create a pool that can accommodate a fixed number of threads. The survival time of each thread is unlimited. When the pool is full, no more threads will be added; if all threads in the pool are busy, new tasks will enter the blocking queue ( Unbounded blocking queue), however, when the thread pool is idle, that is, when there are no runnable tasks in the thread pool, it will not release the worker thread, and it will also occupy certain system resources.

​ Applicable: long-term execution scenarios

4.NewScheduledThreadPool

​ Create a thread pool with a fixed size. There is no limit to the survival time of threads in the thread pool. The thread pool can support timing and periodic task execution. If all threads are busy, new tasks will enter the DelayedWorkQueue queue. This is a method according to Queue structure sorted by timeout

​ Applicable: Periodic execution scenarios

23 How to open a thread, what problems will there be when opening a large number of threads, and how to optimize? (Meituan)

What is this question trying to investigate?

Are you familiar with the problems of opening a large number of threads and using them in real scenarios? Are you familiar with the problems of opening a large number of threads?

Knowledge points of inspection

The concept of what is wrong with opening a large number of threads is used in projects and basic knowledge

How should candidates answer

How to start a thread

How to start a thread, the description in JDK is:

/**
 * ...
 * There are two ways to create a new thread of execution. One is to
 * declare a class to be a subclass of <code>Thread</code>. 
 * The other way to create a thread is to declare a class that
 * implements the <code>Runnable</code> interface.
 * ....
 */
public class Thread implements Runnable{
    
    
      
}

There is such a paragraph in the class description of the Thread source code. To translate it, there are only two ways to create an execution thread. One is to declare a subclass of Thread, and the other is to create a class to implement the Runnable interface.

Inherit the Thread class
public class ThreadUnitTest {
    
    

    @Test
    public void testThread() {
    
    
        //创建MyThread实例
        MyThread myThread = new MyThread();
        //调用线程start的方法,进入可执行状态
        myThread.start();
    }

    //继承Thread类,重写内部run方法
    static class MyThread extends Thread {
    
    

        @Override
        public void run() {
    
    
            System.out.println("test MyThread run");
        }
    }
}
Implement the Runnable interface
public class ThreadUnitTest {
    
    

    @Test
    public void testRunnable() {
    
    
        //创建MyRunnable实例,这其实只是一个任务,并不是线程
        MyRunnable myRunnable = new MyRunnable();
        //交给线程去执行
        new Thread(myRunnable).start();
    }

    //实现Runnable接口,并实现内部run方法
    static class MyRunnable implements Runnable {
    
    

        @Override
        public void run() {
    
    
            System.out.println("test MyRunnable run");
        }
    }
}
Implement Callable

In fact, the way to implement the Callback interface to create threads is in the final analysis the Runnable way, but it adds some capabilities on the basis of Runnable, such as canceling task execution.

public class ThreadUnitTest {
    
    

    @Test
    public void testCallable() {
    
    
        //创建MyCallable实例,需要与FutureTask结合使用
        MyCallable myCallable = new MyCallable();
        //创建FutureTask,与Runnable一样,也只能算是个任务
        FutureTask<String> futureTask = new FutureTask<>(myCallable);
        //交给线程去执行
        new Thread(futureTask).start();

        try {
    
    
            //get方法获取任务返回值,该方法是阻塞的
            String result = futureTask.get();
            System.out.println(result);
        } catch (ExecutionException e) {
    
    
            e.printStackTrace();
        } catch (InterruptedException e) {
    
    
            e.printStackTrace();
        }
    }

    //实现Callable接口,并实现call方法,不同之处是该方法有返回值
    static class MyCallable implements Callable<String> {
    
    

        @Override
        public String call() throws Exception {
    
    
            Thread.sleep(10000);
            return "test MyCallable run";
        }
    }
}

The Callable method must be used in conjunction with FutureTask. Let's look at the inheritance relationship of FutureTask:

//FutureTask实现了RunnableFuture接口
public class FutureTask<V> implements RunnableFuture<V> {
    
    

}

//RunnableFuture接口继承Runnable和Future接口
public interface RunnableFuture<V> extends Runnable, Future<V> {
    
    
    void run();
}

What problems can be caused by opening a large number of threads

In Java, after calling the start method of Thread, the thread is set to the ready state, waiting for the scheduling of the CPU. There are two concerns in this process that need to be understood.

How to start the thread inside start? See how the start method is implemented.

// Thread类的start方法
public synchronized void start() {
    
    
        // 一系列状态检查
        if (threadStatus != 0)
            throw new IllegalThreadStateException();
   
        group.add(this);
           
        boolean started = false;
        try {
    
    
             //调用start0方法,真正启动java线程的地方
            start0();
            started = true;
        } finally {
    
    
            try {
    
    
                if (!started) {
    
    
                 group.threadStartFailed(this);
                }
            } catch (Throwable ignore) {
    
    
            }
        }
    }
   
//start0方法是一个native方法
private native void start0();

In the JVM, there is a mapping relationship between the native method and the java method. start0 in Java corresponds to the JVM_StartThread method of the c layer. Let's continue to look at:

JVM_ENTRY(void, JVM_StartThread(JNIEnv* env, jobject jthread))
  JVMWrapper("JVM_StartThread");
  JavaThread *native_thread = NULL;
  bool throw_illegal_thread_state = false;
  {
    
    
   
    MutexLocker mu(Threads_lock);
    // 判断Java线程是否已经启动,如果已经启动过,则会抛异常。
    if (java_lang_Thread::thread(JNIHandles::resolve_non_null(jthread)) != NULL) {
    
    
      throw_illegal_thread_state = true;
    } else {
    
    
      //如果没有启动过,走到这里else分支,去创建线程
      //分配c++线程结构并创建native线程
      jlong size =
             java_lang_Thread::stackSize(JNIHandles::resolve_non_null(jthread));
   
      size_t sz = size > 0 ? (size_t) size : 0;
      //注意这里new JavaThread
      native_thread = new JavaThread(&thread_entry, sz);
      if (native_thread->osthread() != NULL) {
    
    
        native_thread->prepare(jthread);
      }
    }
  }
  ......
  Thread::start(native_thread);

When I came here, I found that the Java layer has transitioned to the native layer, but it is far from over:

JavaThread::JavaThread(ThreadFunction entry_point, size_t stack_sz) :
                          Thread()
   {
    
    
     initialize();
     _jni_attach_state = _not_attaching_via_jni;
     set_entry_point(entry_point);
     os::ThreadType thr_type = os::java_thread;
     thr_type = entry_point == &compiler_thread_entry ? os::compiler_thread :
                                                        os::java_thread;
     //根据平台,调用create_thread,创建真正的内核线程                       
     os::create_thread(this, thr_type, stack_sz);
   }
   
   bool os::create_thread(Thread* thread, ThreadType thr_type,
                          size_t req_stack_size) {
    
    
       ......
       pthread_t tid;
       //利用pthread_create()来创建线程
       int ret = pthread_create(&tid, &attr, (void* (*)(void*)) thread_native_entry, thread);
       ......
       return true;
}

In the pthread_create method, the third parameter indicates the entry of the method to be executed after starting this thread, and the fourth parameter indicates the parameters to be passed to this method:

static void *thread_native_entry(Thread *thread) {
    
    
  ......
  //thread_native_entry方法的最下面的run方法,这个thread就是上面传递下来的参数,也就是JavaThread
  thread->run();
  ......
  return 0;
}

Finally started to execute the run method:

//thread.cpp类
void JavaThread::run() {
    
    
  ......
  //调用内部thread_main_inner  
  thread_main_inner();
}
   
void JavaThread::thread_main_inner() {
    
    
  if (!this->has_pending_exception() &&
   !java_lang_Thread::is_stillborn(this->threadObj())) {
    
    
    {
    
    
      ResourceMark rm(this);
      this->set_native_thread_name(this->get_thread_name());
    }
    HandleMark hm(this);
    //注意:内部通过JavaCalls模块,调用了Java线程要执行的run方法
    this->entry_point()(this, this);
  }
  DTRACE_THREAD_PROBE(stop, this);
  this->exit(false);
  delete this;
}

A U-shaped code call chain ends here:

  • The star method of calling Thread in Java is called to the native layer through JNI.

  • In the native layer, the JVM creates a system kernel thread through the pthread_create method, and specifies the initial running address of the kernel thread, that is, a method pointer.

  • In the initial running method of the kernel thread, use the JavaCalls module to call back to the run method of the java thread to start java-level thread execution.

How threads are scheduled

In the computer world, the CPU is divided into several time slices, and various algorithms are used to allocate time slices to perform tasks. There are familiar time slice round-robin scheduling algorithms, short process priority algorithms, and priority algorithms. When the time slice of a task runs out, it will switch to another task. The state of the previous task will be saved before switching, and this state will be loaded when switching to the task next time, which is the so-called thread context switching. Obviously, context switching has overhead, including many aspects, the overhead of the operating system saving and restoring the context, the overhead of the thread scheduler scheduling threads, and the overhead of cache reloading.

insert image description here

After reviewing the above two theoretical foundations, the problems caused by opening a large number of threads can be summed up in two words - overhead.

Time-consuming: It takes time to create and destroy threads. When the number is too large, it will affect efficiency.
Consuming memory: Creating more threads will consume more memory, there is no doubt about it. Frequent creation and destruction of threads may also cause memory jitter and frequent triggering of GC. The most direct manifestation is stuttering. Over time, if the memory resources are occupied too much or the memory fragments are too much, the system may even experience OOM.
consumes CPU. In the operating system, the CPU follows the time slice rotation mechanism to process tasks. Too many threads will inevitably cause the CPU to perform thread context switching frequently. This cost is expensive, and in some scenarios even exceeds the consumption of the task itself.

how to optimize

The essence of threads is to perform tasks. In the computer world, tasks are roughly divided into two categories, CPU-intensive tasks and IO-intensive tasks.

CPU-intensive tasks, such as formula calculation, resource decoding, etc. This type of task requires a lot of calculations, all of which rely on the computing power of the CPU and consume CPU resources for a long time. Therefore, for such tasks, a large number of threads should not be opened. Because the more threads, the more time spent on thread switching, and the lower the CPU execution efficiency. Generally, the number of CPU-intensive tasks performed at the same time is equal to the number of CPU cores, plus 1 at most.
IO-intensive tasks, such as network reading and writing, file reading and writing, etc. This type of task does not need to consume too many CPU resources, and most of the time is spent on IO operations. Therefore, for such tasks, a large number of threads can be opened to improve the execution efficiency of the CPU. Generally, the number of concurrent IO-intensive tasks is equal to twice the number of cores of the CPU.
In addition, when it is unavoidable and a large number of threads must be opened, we can also use the thread pool instead of directly creating threads for optimization. The basic function of the thread pool is to reuse existing threads, thereby reducing the creation of threads and reducing overhead. In Java, the use of thread pool is still very convenient. JDK provides a ready-made ThreadPoolExecutor class. We only need to configure the corresponding parameters according to our own needs. Here is an example.

/**
 * 线程池使用
 */
public class ThreadPoolService {
    
    

    /**
     * 线程池变量
     */
    private ThreadPoolExecutor mThreadPoolExecutor;

    private static volatile ThreadPoolService sInstance = null;

    /**
     * 线程池中的核心线程数,默认情况下,核心线程一直存活在线程池中,即便他们在线程池中处于闲置状态。
     * 除非我们将ThreadPoolExecutor的allowCoreThreadTimeOut属性设为true的时候,这时候处于闲置的核心         * 线程在等待新任务到来时会有超时策略,这个超时时间由keepAliveTime来指定。一旦超过所设置的超时时间,闲     * 置的核心线程就会被终止。
     * CPU密集型任务  N+1   IO密集型任务   2*N
     */
    private final int CORE_POOL_SIZE = Runtime.getRuntime().availableProcessors() + 1;
    /**
     * 线程池中所容纳的最大线程数,如果活动的线程达到这个数值以后,后续的新任务将会被阻塞。包含核心线程数+非*      * 核心线程数。
     */
    private final int MAXIMUM_POOL_SIZE = Math.max(CORE_POOL_SIZE, 10);
    /**
     * 非核心线程闲置时的超时时长,对于非核心线程,闲置时间超过这个时间,非核心线程就会被回收。
     * 只有对ThreadPoolExecutor的allowCoreThreadTimeOut属性设为true的时候,这个超时时间才会对核心线       * 程产生效果。
     */
    private final long KEEP_ALIVE_TIME = 2;
    /**
     * 用于指定keepAliveTime参数的时间单位。
     */
    private final TimeUnit UNIT = TimeUnit.SECONDS;
    /**
     * 线程池中保存等待执行的任务的阻塞队列
     * ArrayBlockingQueue  基于数组实现的有界的阻塞队列
     * LinkedBlockingQueue  基于链表实现的阻塞队列
     * SynchronousQueue   内部没有任何容量的阻塞队列。在它内部没有任何的缓存空间
     * PriorityBlockingQueue   具有优先级的无限阻塞队列。
     */
    private final BlockingQueue<Runnable> WORK_QUEUE = new LinkedBlockingDeque<>();
    /**
     * 线程工厂,为线程池提供新线程的创建。ThreadFactory是一个接口,里面只有一个newThread方法。 默认为DefaultThreadFactory类。
     */
    private final ThreadFactory THREAD_FACTORY = Executors.defaultThreadFactory();
    /**
     * 拒绝策略,当任务队列已满并且线程池中的活动线程已经达到所限定的最大值或者是无法成功执行任务,这时候       * ThreadPoolExecutor会调用RejectedExecutionHandler中的rejectedExecution方法。
     * CallerRunsPolicy  只用调用者所在线程来运行任务。
     * AbortPolicy  直接抛出RejectedExecutionException异常。
     * DiscardPolicy  丢弃掉该任务,不进行处理。
     * DiscardOldestPolicy   丢弃队列里最近的一个任务,并执行当前任务。
     */
    private final RejectedExecutionHandler REJECTED_HANDLER = new ThreadPoolExecutor.AbortPolicy();

    private ThreadPoolService() {
    
    
    }

    /**
     * 单例
     * @return
     */
    public static ThreadPoolService getInstance() {
    
    
        if (sInstance == null) {
    
    
            synchronized (ThreadPoolService.class) {
    
    
                if (sInstance == null) {
    
    
                    sInstance = new ThreadPoolService();
                    sInstance.initThreadPool();
                }
            }
        }
        return sInstance;
    }

    /**
     * 初始化线程池
     */
    private void initThreadPool() {
    
    
        try {
    
    
            mThreadPoolExecutor = new ThreadPoolExecutor(
                    CORE_POOL_SIZE,
                    MAXIMUM_POOL_SIZE,
                    KEEP_ALIVE_TIME,
                    UNIT,
                    WORK_QUEUE,
                    THREAD_FACTORY,
                    REJECTED_HANDLER);
        } catch (Exception e) {
    
    
            LogUtil.printStackTrace(e);
        }
    }

    /**
     * 向线程池提交任务,无返回值
     *
     * @param runnable
     */
    public void post(Runnable runnable) {
    
    
        mThreadPoolExecutor.execute(runnable);
    }

    /**
     * 向线程池提交任务,有返回值
     *
     * @param callable
     */
    public <T> Future<T> post(Callable<T> callable) {
    
    
        RunnableFuture<T> task = new FutureTask<T>(callable);
        mThreadPoolExecutor.execute(task);
        return task;
    }
}

24 pthread understand? How much memory does a new thread take up? (quick worker)

What is this question trying to investigate?

Are you aware of the cost of creating threads

Knowledge points of inspection

The underlying principle of threads

thread resource consumption

How should candidates answer

pthread

pthread generally refers to the POSIX thread standard, which is a set of APIs that define the creation and manipulation of threads. Generally used in Unix-like systems, such as Linux, Mac OS.

Java's cross-platform is based on a virtual machine, and the JVM shields the underlying implementation of different operating systems. Creating a thread in Java and running it on Linux (including Android) actually encapsulates pthread ( "4.23 How to start a thread, what problems will there be when opening a large number of threads, and how to optimize it?" ).

thread memory

In Java, each thread needs to allocate thread memory to store its own thread variables. In JDK 1.4, each thread has 256K of memory, and after JDK 1.5, each thread has 1M of memory.

25 What is HandlerThread?

What is this question trying to investigate?

Do you understand the use of HandlerThread and real scenarios, are you familiar with HandlerThread

Knowledge points of inspection

The concept of HandlerThread is used in the project and basic knowledge

How should candidates answer

HandlerThread principle

HandlerThread is a Thread with its own Handler, more precisely, a Thread with Looper inside, which can be known from the source code comments of HandlerThread.

/**
 * Handy class for starting a new thread that has a looper. The looper can then be 
 * used to create handler classes. Note that start() must still be called.
 */

Convenience class for starting a new thread with a looper. A looper can be used to create handler classes. Note that start() must still be called to start.

To get a Handler bound to it, you can get it through the built-in getThreadHandler() method.

public Handler getThreadHandler() {
    
    
	if (mHandler == null) {
    
    
		mHandler = new Handler(getLooper());
	}
	return mHandler;
}

So, what happens internally from the moment the handlerThread.start() method is called? Thread's start() method is annotated as follows.

/**
 * Causes this thread to begin execution; the Java Virtual Machine
 * calls the <code>run</code> method of this thread.
 */

Causes this thread to begin executing; the Java virtual machine calls this thread's run method.

Take a look at the run() method of HandlerThread.

@Override
public void run() {
    
    
	mTid = Process.myTid();
    Looper.prepare();
    synchronized (this) {
    
    
    	mLooper = Looper.myLooper();
    	notifyAll();
    }
    Process.setThreadPriority(mPriority);
    onLooperPrepared();
    Looper.loop();
    mTid = -1;
}

There is nothing strange in the run() method. First, the Looper.prepare() method is called to create a Looper, and then a synchronization lock is added to obtain the Looper object of the current thread and assigned to mLooper and wake up all threads, and then set the current thread Priority, then call the onLooperPrepared() method, and finally call Looper.loop() to start the loop.

You may be a little curious why you need to add a synchronization lock when assigning mLooper, which will be explained later.

Seeing this, you may think, is this the same as inheriting Thread and opening a Looper in the run method? Indeed, but the official also said that HandlerThread is a convenient class, which is convenient for us to use Handler in sub-threads.

By the way, what does the onLooperPrepared() method in the above run() method do?

/**
 * Call back method that can be explicitly overridden if needed to execute some
 * setup before Looper loops.
 */
 protected void onLooperPrepared() {
    
    
 }

If you need to perform some setup before the Looper loop, you can override this method.

emmm, why is the synchronization lock added to the run() method?

If you don't want to get a Handler through the getThreadHandler() method provided by HandlerThread, HandlerThread also provides the getLooper() method to provide you with a Looper object to create your own Handler.

	public Looper getLooper() {
    
    
        if (!isAlive()) {
    
    
            return null;
        }
        
        // If the thread has been started, wait until the looper has been created.
        synchronized (this) {
    
    
            while (isAlive() && mLooper == null) {
    
    
                try {
    
    
                    wait();
                } catch (InterruptedException e) {
    
    
                }
            }
        }
        return mLooper;
    }

Now I understand why all threads must be woken up after the assignment of mLooper in the run() method synchronization lock is completed. Because this method will block the thread until the looper has been initialized.

The entire HandlerThread class is less than 200 lines of code. In addition to the above, it also encapsulates the method of exiting the Looper when the thread is not in use.

	public boolean quit() {
    
    
        Looper looper = getLooper();
        if (looper != null) {
    
    
            looper.quit();
            return true;
        }
        return false;
    }
	public boolean quitSafely() {
    
    
        Looper looper = getLooper();
        if (looper != null) {
    
    
            looper.quitSafely();
            return true;
        }
        return false;
    }

It can be seen that HandlerThread is really a convenient class, very convenient.

scenes to be used

Why should I use HandlerThread compared to ordinary Thread?

For ordinary Thread, it ends after one execution. An instance cannot be started multiple times. If you start the same example more than once, an IllegalThreadStateException will be thrown. These are all commented in the source code of Thread.

/**
 * It is never legal to start a thread more than once.
 * 不止一次启动线程永远不合法
 * In particular, a thread may not be restarted once it has completed execution.
 * 特别是,一旦完成执行,线程可能无法重新启动。
 * @exception  IllegalThreadStateException  if the thread was already started.         
 *             如果线程已经启动将抛出IllegalThreadStateException异常
 */

If you need to frequently operate databases, large files, and request networks, you need to frequently create Thread examples to call start(), or you need to perform a series of serial operations; and because HandlerThread has its own Handler, it also comes with a In the task queue, when time-consuming operations are required, you only need to execute the send or post series of operations through the Handler, and there is no need to frequently create Threads to save resources.

at last

This interview question will continue to be updated, please pay attention! ! !

Friends who need this interview question can scan the QR code below to get it for free! ! !
(Scanning the QR code can also enjoy the service of the ChatGPT robot!!!)

Guess you like

Origin blog.csdn.net/datian1234/article/details/131614712