线程和线程池相关总结

1,总结

总结下线程和线程池相关的使用和知识

2,线程

在 Android 中,线程是用于执行并发任务的基本单位。Android 提供了多种方式来创建和管理线程,以满足不同的需求。

以下是在 Android 中使用线程的几种常见方式:

  1. Thread 类:可以直接使用 Java 的 Thread 类来创建和管理线程。你可以继承 Thread 类并重写 run() 方法来定义线程的执行逻辑。例如:
java 复制代码
Thread thread = new Thread() {
    @Override
    public void run() {
        // 线程的执行逻辑
    }
};
thread.start(); // 启动线程
  1. Runnable 接口:可以实现 Runnable 接口,并将实现逻辑放在 run() 方法中。然后,通过 Thread 类来创建线程并传入 Runnable 对象。例如:
java 复制代码
Runnable runnable = new Runnable() {
    @Override
    public void run() {
        // 线程的执行逻辑
    }
};
Thread thread = new Thread(runnable);
thread.start(); // 启动线程
  1. AsyncTask 类:AsyncTask 是一个方便的异步任务类,它可以简化在后台执行任务并在主线程更新 UI 的操作。AsyncTask 定义了 doInBackground() 方法来执行后台任务,和 onPostExecute() 方法来在任务完成后更新 UI。例如:
scala 复制代码
java复制
public class MyTask extends AsyncTask<Void, Void, String> {
    @Override
    protected String doInBackground(Void... voids) {
        // 后台任务执行逻辑
        return "result";
    }

    @Override
    protected void onPostExecute(String result) {
        // 更新 UI 的逻辑
    }
}

MyTask task = new MyTask();
task.execute(); // 执行任务
  1. Handler 和 Looper:Handler 和 Looper 是 Android 中用于线程间通信和管理的重要组件。Handler 可以用来发送和处理消息,而 Looper 则负责循环处理消息队列。可以在主线程中创建一个 Handler,然后使用 post()、sendMessage() 等方法来向 Handler 发送消息,在 handleMessage() 方法中处理消息。例如:
typescript 复制代码
java复制
Handler handler = new Handler(Looper.getMainLooper()) {
    @Override
    public void handleMessage(Message msg) {
        // 处理消息的逻辑
    }
};
handler.post(new Runnable() {
    @Override
    public void run() {
        // 在主线程中执行的逻辑
    }
});

需要注意的是,在 Android 中有一些特殊的线程,如主线程(UI 线程)和后台线程。主线程用于处理用户界面操作和更新,而后台线程用于执行耗时操作,以避免阻塞主线程和导致 ANR(应用无响应)的问题。

上述是 Android 中使用线程的几种常见方式,选择合适的方式取决于具体的需求和场景。同时,需要注意在多线程编程中处理线程同步、线程安全和避免内存泄漏等问题。

线程的状态

3,线程池

3.1 线程池的好处

1,线程管理:线程池可以有效的管理线程资源,避免线程创建销毁的开销,可以控制线程的数量,并且重用线程,提高资源利用率,减少系统资源消耗

2,提升性能:通过合理设置线程数量大小,可以根据系统处理能力协调任务执行,提高系统响应速度和性能表现

3,提高代码简洁性:使用线程池可以将任务的提交和执行分离,将并发细节交给线程池来管理,简洁代码

3.2 我们在安卓中常用有四种线程池

1,newFixedThreadPool

fixedThreadPool固定线程池,其中核心线程是固定的,如果所有线程都处于忙碌状态, 新的任务会在对列中等待,直到有线程空闲出来执行任务。

js 复制代码
private void newFixedThreadPool() {
   //在上述示例中,我们创建了一个固定大小为 5 的线程池,并提交了 10 个任务给线程池执行。
   // 由于线程池的大小是固定的,只有 5 个线程会同时执行任务,剩余的任务会在队列中等待。
   ExecutorService executor = Executors.newFixedThreadPool(5);

   for (int i = 0; i < 10; i++) {
      final int task = i;
      executor.execute(new Runnable() {
         @Override
         public void run() {
            // 执行任务的逻辑
            Log.e("tag","===============Task " + task + " is running");
         }
      });
   }

   executor.shutdown();
}

2,cachedThreadPool

CachedThreadPool(缓存线程池): CachedThreadPool 是一个大小可变的线程池,它根据需要创建新的线程,如果有线程空闲一段时间没有被使用,则会被回收。

js 复制代码
private void cachedThreadPool() {
   ExecutorService executor = Executors.newCachedThreadPool();

   for (int i = 0; i < 10; i++) {
      final int task = i;
      executor.execute(new Runnable() {
         @Override
         public void run() {
            // 执行任务的逻辑
            Log.e("tag","===============Task " + task + " is running");
         }
      });
   }

   executor.shutdown();
}

3,scheduledThreadPool

ScheduledThreadPool(定时线程池): ScheduledThreadPool 是一个可以执行定时任务的线程池,它可以在指定的延迟时间后执行任务,或者以固定的频率重复执行任务。

typescript 复制代码
private void scheduledThreadPool() {
   //在上述示例中,我们创建了一个包含两个线程的定时线程池,并提交了两个任务给线程池执行。
   // 第一个任务将在 5 秒后执行一次,第二个任务将每隔 2 秒执行一次。
   ScheduledExecutorService executor = Executors.newScheduledThreadPool(2);

   executor.schedule(new Runnable() {
      @Override
      public void run() {
         // 执行任务的逻辑
         Log.e("tag","===============Task is running after 5 seconds");

      }
   }, 5, TimeUnit.SECONDS);

   executor.scheduleAtFixedRate(new Runnable() {
      @Override
      public void run() {
         // 执行任务的逻辑
         Log.e("tag","===============Task is running every 2 seconds");
      }
   }, 0, 2, TimeUnit.SECONDS);

   // 等待任务执行
   executor.shutdown();
}

4,singleThreadPool

SingleThreadPool(单线程池): SingleThreadPool 是一个只有一个线程的线程池, 所有任务都按照顺序执行,保证线程的安全性。

java 复制代码
private void singleThreadPool() {
   //在上述示例中,我们创建了一个只有一个线程的线程池,并提交了 10 个任务给线程池执行。
   // 由于线程池中只有一个线程,保证了所有任务按照顺序执行。
   ExecutorService executor = Executors.newSingleThreadExecutor();

   for (int i = 0; i < 10; i++) {
      final int task = i;
      executor.execute(new Runnable() {
         @Override
         public void run() {
            // 执行任务的逻辑
            Log.e("tag","===============Task " + task + " is running");
         }
      });
   }

   executor.shutdown();
}

我们可以看到四种常用的线程池的创建,我们可以根据情况选择合适的线程池使用,同时我们也可以自定义线程池

3.3 自定义线程池

java 复制代码
private ThreadFactory sIOThreadFactory = new ThreadFactory() {
   private final AtomicInteger mCount = new AtomicInteger(1);

   @Override
   public Thread newThread(Runnable r) {
      return new Thread(r,"IOThread#"+mCount.getAndIncrement());
   }
};
private ExecutorService mIOThreadPool = null;
//自定义线程池以及线程池工具类方法
private void testThread2() {

   if(mIOThreadPool == null){
      int CPU_COUNT = Runtime.getRuntime().availableProcessors();
      int MAXIMUM_POOL_SIZE = CPU_COUNT*2 + 1;
      long KEEP_ALIVE = 1000L;
      mIOThreadPool = new ThreadPoolExecutor(
              4,
              Math.max(4, MAXIMUM_POOL_SIZE),
              KEEP_ALIVE,
              TimeUnit.MILLISECONDS,
              new LinkedBlockingDeque<Runnable>(),
              sIOThreadFactory);
   }

   for (int i = 0; i < 10; i++) {
      final int task = i;
      mIOThreadPool.execute(new Runnable() {
         @Override
         public void run() {
            // 执行任务的逻辑
            Log.e("tag","===============Task " + task + " is running");
         }
      });
   }

   mIOThreadPool.shutdown();
}

通过上面代码,我们可以通过ThreadPoolExecutor自定义线程池,这个时候,我们可以查看下上面四个线程池的源码

1,newFixedThreadPool

arduino 复制代码
public static ExecutorService newFixedThreadPool(int nThreads) {
    return new ThreadPoolExecutor(nThreads, 
                                  nThreads,
                                  0L, 
                                  TimeUnit.MILLISECONDS,
                                  new LinkedBlockingQueue<Runnable>());
}

2,cachedThreadPool

csharp 复制代码
public static ExecutorService newCachedThreadPool() {
    return new ThreadPoolExecutor(0, 
                                  Integer.MAX_VALUE,
                                  60L, 
                                  TimeUnit.SECONDS,
                                  new SynchronousQueue<Runnable>());
}

3,scheduledThreadPool

java 复制代码
public ScheduledThreadPoolExecutor(int corePoolSize) {
    super(corePoolSize, 
          Integer.MAX_VALUE,
          DEFAULT_KEEPALIVE_MILLIS,
          MILLISECONDS,
          new DelayedWorkQueue());
}

4,singleThreadPool

csharp 复制代码
public static ExecutorService newSingleThreadExecutor() {
    return new FinalizableDelegatedExecutorService
        (new ThreadPoolExecutor(1, 
                                1,
                                0L, 
                                TimeUnit.MILLISECONDS,
                                new LinkedBlockingQueue<Runnable>()));
}

我们可以看到常用的四种线程池和自定义线程池他们都是通过ThreadPoolExecutor来创建的,只是传入的参数不一样,接下来我们对ThreadPoolExecutor进行一些研究

ini 复制代码
public ThreadPoolExecutor(int corePoolSize,
                          int maximumPoolSize,
                          long keepAliveTime,
                          TimeUnit unit,
                          BlockingQueue<Runnable> workQueue,
                          ThreadFactory threadFactory,
                          RejectedExecutionHandler handler) {
    if (corePoolSize < 0 ||
        maximumPoolSize <= 0 ||
        maximumPoolSize < corePoolSize ||
        keepAliveTime < 0)
        throw new IllegalArgumentException();
    if (workQueue == null || threadFactory == null || handler == null)
        throw new NullPointerException();
    this.corePoolSize = corePoolSize;
    this.maximumPoolSize = maximumPoolSize;
    this.workQueue = workQueue;
    this.keepAliveTime = unit.toNanos(keepAliveTime);
    this.threadFactory = threadFactory;
    this.handler = handler;
}

我们看下ThreadPoolExecutor传入了7个参数,这些参数分别代表了

1,corePoolSize(核心线程数):线程池中保持运行的线程数量,即使线程是空闲的。如果任务数量超过核心线程数,线程池会创建新的线程来处理任务,直到达到最大线程数。

2,maximumPoolSize(最大线程数):线程池中允许的最大线程数量。如果任务数量超过最大线程数,并且任务队列已满,线程池会根据拒绝策略来处理无法执行的任务

3, keepAliveTime(线程空闲时间):当线程池中的线程数量超过核心线程数时,空闲线程的存活时间。超过这个时间,多余的线程会被销毁,保持线程池的大小不超过核心线程数。

4, unit(时间单位):用于指定 keepAliveTime 的时间单位,如 TimeUnit.SECONDS 表示秒。

5, workQueue(任务队列):用于存储等待执行的任务的阻塞队列。当线程池中的线程都在执行任务时,新提交的任务会被存储在任务队列中等待执行。

6,threadFactory(线程工厂):用于创建新线程的工厂。通过自定义线程工厂,可以对线程进行一些额外的设置,如命名、优先级等。

7,handler(拒绝策略):当任务无法被线程池执行时的处理策略。常见的拒绝策略有四种:AbortPolicy(默认),抛出 RejectedExecutionExceptionCallerRunsPolicy,由提交任务的线程来执行任务;DiscardPolicy,直接丢弃无法执行的任务;DiscardOldestPolicy,丢弃队列中最旧的任务,然后尝试重新提交当前任务。

线程池不需要重复创建,过多创建线程池容易发生资源泄露,因此可以使用单例模式创建一次即可。线程池的线程数理论上满足2*cpu核心数+1的时候性能最佳

线程池的最大线程数,可以根据执行的不同任务场景选择不同的最大任务数

1,CPU密集型任务:N(CPU 核心数)+1

这种任务消耗的主要是 CPU 资源,可以将线程数设置为 N(CPU 核心数)+1,比 CPU 核心数多出来的一个线程是为了防止线程偶发的缺页中断,或者其它原因导致的任务暂停而带来的影响。

一旦任务暂停,CPU 就会处于空闲状态,而在这种情况下多出来的一个线程就可以充分利用 CPU 的空闲时间。

2,I/O密集型

这种任务应用起来,系统会用大部分的时间来处理 I/O 交互,而线程在处理 I/O 的时间段内不会占用 CPU 来处理,这时就可以将 CPU 交出给其它线程使用。因此在 I/O 密集型任务的应用中,我们可以多配置一些线程,具体的计算方法是 2N。

线程池任务执行的顺序

我们可以看下ThreadPoolExecutor中execute的执行方法

js 复制代码
public void execute(Runnable command) {
    if (command == null)
        throw new NullPointerException();
    int c = ctl.get();
    if (workerCountOf(c) < corePoolSize) {
        if (addWorker(command, true))
            return;
        c = ctl.get();
    }
    if (isRunning(c) && workQueue.offer(command)) {
        int recheck = ctl.get();
        if (! isRunning(recheck) && remove(command))
            reject(command);
        else if (workerCountOf(recheck) == 0)
            addWorker(null, false);
    }
    else if (!addWorker(command, false))
        reject(command);
}

其中线程任务队列有哪些呢?

有界队列和无界队列

1,有界队列:可以指定最大容量

常见的有界队列有:

ArrayBlockingQueue:这是一个基于数组的有界阻塞队列。任务会按照先进先出的顺序进行执行。它可以指定一个固定的最大容量,超过这个容量的任务将无法加入队列,需要等待队列中的任务被取出后才能继续添加。如果线程池中的线程数小于核心线程数,新提交的任务将会创建新线程来执行,直到达到核心线程数。

2,无界队列:可以无限制地添加任务

常见的无界队列有:

LinkedBlockingQueue:这是一个基于链表的无界阻塞队列。任务会按照先进先出(FIFO)的顺序进行执行。如果任务数量超过核心线程数,超出的任务会被放入队列中,等待线程池中的线程来执行。

SynchronousQueue:这是一个没有缓冲的队列,任务会直接交付给线程进行执行。如果没有空闲的线程来处理任务,新提交的任务将会被拒绝。适用于任务量较小、执行时间短的场景

不同的任务队列对线程池的行为和特性有影响,主要体现在以下几点:

  1. 容量限制:LinkedBlockingQueue是无界队列,可以无限制地添加任务;ArrayBlockingQueue是有界队列,可以指定最大容量;SynchronousQueue没有容量,无法缓冲任务。
  2. 任务顺序:LinkedBlockingQueueArrayBlockingQueue都是按照先进先出的顺序执行任务,而SynchronousQueue没有缓冲,任务会直接交付给线程进行执行。
  3. 线程数控制:LinkedBlockingQueueArrayBlockingQueue均通过控制线程数来处理任务,超过核心线程数的任务会被放入队列中等待执行。而SynchronousQueue在没有空闲线程时,会拒绝新的任务提交。

选择适合的任务队列取决于具体的应用需求和场景。如果任务量较大或者任务执行时间较长,可以选择无界队列;如果需要限制队列的容量,可以选择有界队列;如果希望直接将任务交付给线程执行,可以选择没有缓冲的队列。

那么对上面常见四种线程池的执行我们在了解了线程池参数的含义以后,也明白了他们的执行逻辑和使用场景

1,newFixedThreadPool

newFixedThreadPool的参数 ThreadPoolExecutor(nThreads, nThreads,0L,TimeUnit.MILLISECONDS,new LinkedBlockingQueue())

核心线程数:nThreads

最大线程数:nThreads 核心和最大一样

线程空闲时间:0

时间单位:毫秒

任务队列:LinkedBlockingQueue,无界队列,先进先出执行

适用场景:需要控制并发线程数量且任务执行时间较长的情况,如网络请求、数据库操作等。

2,cachedThreadPool

cachedThreadPool的参数 ThreadPoolExecutor(0,Integer.MAX_VALUE,60L,TimeUnit.SECONDS,new SynchronousQueue())

核心线程数:0

最大线程数:Integer.MAX_VALUE

线程空闲时间:60L

时间单位:秒

任务队列:SynchronousQueue,这是一个没有缓冲的队列,任务会直接交付给线程进行执行

适用场景:需要执行大量短期的任务,如并发处理较多的轻量级任务。

3,scheduledThreadPool

scheduledThreadPool的参数 super(corePoolSize,Integer.MAX_VALUE,DEFAULT_KEEPALIVE_MILLIS,MILLISECONDS, new DelayedWorkQueue())

核心线程数:corePoolSize

最大线程数:Integer.MAX_VALUE

线程空闲时间:DEFAULT_KEEPALIVE_MILLIS,10L

时间单位:毫秒

任务队列:DelayedWorkQueue,

在Android线程池中,DelayedWorkQueue是一种实现了Delayed接口的延迟任务队列。它用于存放具有延迟执行时间的任务,并且会根据任务的延迟时间进行排序。DelayedWorkQueue是基于PriorityQueue实现的,所以它具有队列元素按照优先级排序的特性。

DelayedWorkQueue的主要特点如下:

  1. 延迟执行:DelayedWorkQueue中存放的任务具有延迟执行的特性。每个任务都有一个延迟时间,并且任务必须实现Delayed接口,提供getDelay(TimeUnit unit)方法,用于返回任务延迟的剩余时间。只有当延迟时间过去之后,任务才会被取出并执行。
  2. 优先级排序:DelayedWorkQueue基于PriorityQueue实现,任务队列中的任务按照优先级进行排序。优先级通过比较任务的延迟时间来确定,延迟时间较短的任务具有更高的优先级,会在队列中排在前面等待执行。
  3. 无界队列:DelayedWorkQueue没有固定的容量限制,可以无限制地添加任务。这意味着你可以根据需要动态地添加任务到队列中。

DelayedWorkQueue适用于需要延迟执行任务的场景,比如定时任务、任务调度等。通过使用DelayedWorkQueue,你可以方便地控制任务的延迟执行时间,并按照优先级顺序执行任务。

适用场景:需要按照固定顺序执行任务,保证任务的有序性。

4,singleThreadPool

singleThreadPool的参数 new FinalizableDelegatedExecutorService (new ThreadPoolExecutor(1, 1, 0L, TimeUnit.MILLISECONDS, new LinkedBlockingQueue()))

核心线程数:1

最大线程数:1

线程空闲时间:0L

时间单位:毫秒

任务队列:LinkedBlockingQueue,无界队列,先进先出执行

适用场景:需要定时执行任务或者周期性执行任务的场景,如定时刷新数据、定时发送通知等。

相关推荐
ZJ_.12 分钟前
WPSJS:让 WPS 办公与 JavaScript 完美联动
开发语言·前端·javascript·vscode·ecmascript·wps
GIS开发特训营16 分钟前
Vue零基础教程|从前端框架到GIS开发系列课程(七)响应式系统介绍
前端·vue.js·前端框架·gis开发·webgis·三维gis
Cachel wood42 分钟前
python round四舍五入和decimal库精确四舍五入
java·linux·前端·数据库·vue.js·python·前端框架
学代码的小前端43 分钟前
0基础学前端-----CSS DAY9
前端·css
joan_851 小时前
layui表格templet图片渲染--模板字符串和字符串拼接
前端·javascript·layui
m0_748236111 小时前
Calcite Web 项目常见问题解决方案
开发语言·前端·rust
Watermelo6172 小时前
详解js柯里化原理及用法,探究柯里化在Redux Selector 的场景模拟、构建复杂的数据流管道、优化深度嵌套函数中的精妙应用
开发语言·前端·javascript·算法·数据挖掘·数据分析·ecmascript
m0_748248942 小时前
HTML5系列(11)-- Web 无障碍开发指南
前端·html·html5
m0_748235612 小时前
从零开始学前端之HTML(三)
前端·html
一个处女座的程序猿O(∩_∩)O4 小时前
小型 Vue 项目,该不该用 Pinia 、Vuex呢?
前端·javascript·vue.js