Android提供了四种常用的操作多线程的方式,分别是:
1. Handler+Thread
2. AsyncTask
3. ThreadPoolExecutor
4. IntentService
其中 handler 机制和 AsyncTask异步任务机制 可以查看 博客
1. ThreadPoolExecutor 线程池的使用
为什么要使用线程池技术:
一方面减少了每个并行任务独自建立线程的开销,另一方面可以管理多个并发线程的公共资源,从而提高了多线程的效率。所以ThreadPoolExecutor比较适合一组任务的执行。Executors利用工厂模式对ThreadPoolExecutor进行了封装,使用起来更加便。
1) ThreadPoolExecutor构造函数
Executor作为一个接口,它的具体实现就是ThreadPoolExecutor,看下它的构造函数:
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 threadPoolExecutor = new ThreadPoolExecutor(3, 30, 1,
TimeUnit.MINUTES, new LinkedBlockingDeque<Runnable>(128));
1-1)构造方法中的字段含义如下:
-
corePoolSize:
线程池大小,决定着新提交的任务是新开线程去执行还是放到任务队列中,也是线程池的最最核心的参数。一般线程池开始时是没有线程的,只有当任务来了并且线程数量小于corePoolSize才会创建线程。
核心线程数量,当有新任务在execute()方法提交时,会执行以下判断:
所以,任务提交时,判断的顺序为 corePoolSize --> workQueue --> maximumPoolSize。
- 如果运行的线程少于 corePoolSize,则创建新线程来处理任务,即使线程池中的其他线程是空闲的;
- 如果线程池中的线程数量大于等于 corePoolSize 且小于 maximumPoolSize,则只有当workQueue满时才创建新的线程去处理任务;
- 如果设置的corePoolSize 和 maximumPoolSize相同,则创建的线程池的大小是固定的,这时如果有新任务提交,若workQueue未满,则将请求放入workQueue中,等待有空闲的线程去从workQueue中取任务并处理;
- 如果运行的线程数量大于等于maximumPoolSize,这时如果workQueue已经满了,则通过handler所指定的策略来处理任务;
- maximumPoolSize:最大线程数量,线程池能创建的最大线程数量。
- workQueue:保存等待执行的任务的阻塞队列,当提交一个新的任务到线程池以后, 线程池会根据当前线程池中正在运行着的线程的数量来决定对该任务的处理方式.,当任务提交时,如果线程池中的线程数量大于等于corePoolSize的时候,把该任务封装成一个Worker对象放入等待队列;主要有以下几种处理方式:
1.ArrayBlockingQueue:这个表示一个规定了大小的BlockingQueue,ArrayBlockingQueue的构造函数接受一个int类型的数据,该数据表示BlockingQueue的大小,存储在ArrayBlockingQueue中的元素按照FIFO(先进先出)的方式来进行存取。
2.LinkedBlockingQueue:这个表示一个大小不确定的BlockingQueue,在LinkedBlockingQueue的构造方法中可以传一个int类型的数据,这样创建出来的LinkedBlockingQueue是有大小的,也可以不传,不传的话,LinkedBlockingQueue的大小就为Integer.MAX_VALUE
3.PriorityBlockingQueue:这个队列和LinkedBlockingQueue类似,不同的是PriorityBlockingQueue中的元素不是按照FIFO来排序的,而是按照元素的Comparator来决定存取顺序的(这个功能也反映了存入PriorityBlockingQueue中的数据必须实现了Comparator接口)。
4.SynchronousQueue:这个是同步Queue,属于线程安全的BlockingQueue的一种,在SynchronousQueue中,生产者线程的插入操作必须要等待消费者线程的移除操作,Synchronous内部没有数据缓存空间,因此我们无法对SynchronousQueue进行读取或者遍历其中的数据,元素只有在你试图取走的时候才有可能存在。我们可以理解为生产者和消费者互相等待,等到对方之后然后再一起离开。
- keepAliveTime:线程池维护线程所允许的空闲时间。当线程池中的线程数量大于corePoolSize的时候,如果这时没有新的任务提交,核心线程外的线程不会立即销毁,而是会等待,直到等待的时间超过了keepAliveTime;
- threadFactory:它是ThreadFactory类型的变量,用来创建新线程。默认使用Executors.defaultThreadFactory() 来创建线程。使用默认的ThreadFactory来创建线程时,会使新创建的线程具有相同的NORM_PRIORITY优先级并且是非守护线程,同时也设置了线程的名称。
- handler:它是RejectedExecutionHandler类型的变量,表示线程池的饱和策略。如果阻塞队列满了并且没有空闲的线程,这时如果继续提交任务,就需要采取一种策略处理该任务。线程池提供了4种策略:
- AbortPolicy:直接抛出异常,这是默认策略;
- CallerRunsPolicy:用调用者所在的线程来执行任务;
- DiscardOldestPolicy:丢弃阻塞队列中靠最前的任务,并执行当前任务;
- DiscardPolicy:直接丢弃任务;
- unit:时间单位
1-2)execute()方法用来提交任务,执行流程如下:
- 如果
workerCount < corePoolSize
,则创建并启动一个线程来执行新提交的任务; - 如果
workerCount >= corePoolSize
,且线程池内的阻塞队列未满,则将任务添加到该阻塞队列中; - 如果
workerCount >= corePoolSize && workerCount < maximumPoolSize
,且线程池内的阻塞队列已满,则创建并启动一个线程来执行新提交的任务; - 如果
workerCount >= maximumPoolSize
,并且线程池内的阻塞队列已满, 则根据拒绝策略来处理该任务, 默认的处理方式是直接抛异常。
public void execute(Runnable command) {
if (command == null)
throw new NullPointerException();
/*
* clt记录着runState和workerCount
*/
int c = ctl.get();
/*
* workerCountOf方法取出低29位的值,表示当前活动的线程数;
* 如果当前活动线程数小于corePoolSize,则新建一个线程放入线程池中;
* 并把任务添加到该线程中。
*/
if (workerCountOf(c) < corePoolSize) {
/*
* addWorker中的第二个参数表示限制添加线程的数量是根据corePoolSize来判断还是maximumPoolSize来判断;
* 如果为true,根据corePoolSize来判断;
* 如果为false,则根据maximumPoolSize来判断
*/
if (addWorker(command, true))
return;
/*
* 如果添加失败,则重新获取ctl值
*/
c = ctl.get();
}
/*
* 如果当前线程池是运行状态并且任务添加到队列成功
*/
if (isRunning(c) && workQueue.offer(command)) {
// 重新获取ctl值
int recheck = ctl.get();
// 再次判断线程池的运行状态,如果不是运行状态,由于之前已经把command添加到workQueue中了,
// 这时需要移除该command
// 执行过后通过handler使用拒绝策略对该任务进行处理,整个方法返回
if (! isRunning(recheck) && remove(command))
reject(command);
/*
* 获取线程池中的有效线程数,如果数量是0,则执行addWorker方法
* 这里传入的参数表示:
* 1. 第一个参数为null,表示在线程池中创建一个线程,但不去启动;
* 2. 第二个参数为false,将线程池的有限线程数量的上限设置为maximumPoolSize,添加线程时根据maximumPoolSize来判断;
* 如果判断workerCount大于0,则直接返回,在workQueue中新增的command会在将来的某个时刻被执行。
*/
else if (workerCountOf(recheck) == 0)
addWorker(null, false);
}
/*
* 如果执行到这里,有两种情况:
* 1. 线程池已经不是RUNNING状态;
* 2. 线程池是RUNNING状态,但workerCount >= corePoolSize并且workQueue已满。
* 这时,再次调用addWorker方法,但第二个参数传入为false,将线程池的有限线程数量的上限设置为maximumPoolSize;
* 如果失败则拒绝该任务
*/
else if (!addWorker(command, false))
reject(command);
}
其中 AsyncTask使用了线程池机制, 部分源码如下:
public abstract class AsyncTask<Params, Progress, Result> {
private static final String LOG_TAG = "AsyncTask";
private static final int CPU_COUNT = Runtime.getRuntime().availableProcessors();
private static final int CORE_POOL_SIZE = CPU_COUNT + 1;
private static final int MAXIMUM_POOL_SIZE = CPU_COUNT * 2 + 1;
private static final int KEEP_ALIVE = 1;
private static final ThreadFactory sThreadFactory = new ThreadFactory() {
private final AtomicInteger mCount = new AtomicInteger(1);
public Thread newThread(Runnable r) {
return new Thread(r, "AsyncTask #" + mCount.getAndIncrement());
}
};
private static final BlockingQueue<Runnable> sPoolWorkQueue =
new LinkedBlockingQueue<Runnable>(128);
/**
* An {@link Executor} that can be used to execute tasks in parallel.
*/
public static final Executor THREAD_POOL_EXECUTOR
= new ThreadPoolExecutor(CORE_POOL_SIZE, MAXIMUM_POOL_SIZE, KEEP_ALIVE,
TimeUnit.SECONDS, sPoolWorkQueue, sThreadFactory);
....
....
}
其中:核心线程数为手机CPU数量+1(cpu数量获取方式Runtime.getRuntime().availableProcessors()),最大线程数为手机CPU数量×2+1,线程队列的大小为128
2) 常用的四种线程池
Executors提供了四种创建ExecutorService的方法, 如下:
1). Executors.newFixedThreadPool()
创建一个定长的线程池,每提交一个任务就创建一个线程,直到达到池的最大长度,这时线程池会保持长度不再变化
2). Executors.newCachedThreadPool()
创建一个可缓存的线程池,如果当前线程池的长度超过了处理的需要时,它可以灵活的回收空闲的线程,当需要增加时,
它可以灵活的添加新的线程,而不会对池的长度作任何限制
3). Executors.newScheduledThreadPool()
创建一个定长的线程池,而且支持定时的以及周期性的任务执行,类似于Timer
4). Executors.newSingleThreadExecutor()
创建一个单线程化的executor,它只创建唯一的worker线程来执行任务
3) 线程池使用的几个方法
- shutDown(),关闭线程池,需要执行完已提交的任务;
- shutDownNow(),关闭线程池,并尝试结束已提交的任务;
- allowCoreThreadTimeOut(boolen),允许核心线程闲置超时回收;
- execute(),提交任务无返回值;
- submit(),提交任务有返回值;
submit() 可以得到返回值,使用方法如下:
public void submit(View view) {
List<Future<String>> futures = new ArrayList<>();
ThreadPoolExecutor threadPoolExecutor = new ThreadPoolExecutor(3, 5, 1,
TimeUnit.SECONDS, new LinkedBlockingDeque<Runnable>());
for (int i = 0; i < 10; i++) {
Future<String> taskFuture = threadPoolExecutor.submit(new MyTask(i));
//将每一个任务的执行结果保存起来
futures.add(taskFuture);
}
try {
//遍历所有任务的执行结果
for (Future<String> future : futures) {
Log.d("google_lenve_fb", "submit: " + future.get());
}
} catch (InterruptedException e) {
e.printStackTrace();
} catch (ExecutionException e) {
e.printStackTrace();
}
}
class MyTask implements Callable<String> {
private int taskId;
public MyTask(int taskId) {
this.taskId = taskId;
}
@Override
public String call() throws Exception {
SystemClock.sleep(1000);
//返回每一个任务的执行结果
return "call()方法被调用----" + Thread.currentThread().getName() + "-------" + taskId;
}
}
使用submit时我们可以通过实现Callable接口来实现异步任务。在call方法中执行异步任务,返回值即为该任务的返回值。Future是返回结果,返回它的isDone属性表示异步任务执行成功
4)newFixedThreadPool() 固定线程数量的线程池
public static ExecutorService newFixedThreadPool(int nThreads) {
return new ThreadPoolExecutor(nThreads, nThreads,
0L, TimeUnit.MILLISECONDS,
new LinkedBlockingQueue<Runnable>());
}
该线程池的特点是:
nThreads 核心线程数和最大线程数是一样的。
用户设置的参数为:核心线程数,只有核心线程,无非核心线程,并且阻塞队列无界。线程的超时时间为0,说明核心线程即使在没有任务可执行的时候也不会被销毁(这样可让FixedThreadPool更快速的响应请求)
创建该线程池,创建5个核心线程:
ExecutorService newFixedThreadPool = Executors.newFixedThreadPool(5);
使用方法:
package com.example.newdemo.threadpool;
import androidx.appcompat.app.AppCompatActivity;
import android.os.Bundle;
import android.util.Log;
import android.view.View;
import android.widget.Button;
import com.example.newdemo.R;
import com.example.newdemo.util.ToastManager;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
public class ThreadPoolActivity extends AppCompatActivity {
private ExecutorService newFixedThreadPool;
private Button btn_newfixed;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_thread_pool);
btn_newfixed = findViewById(R.id.btn_newfixed);
newFixedThreadPool = Executors.newFixedThreadPool(5);
btn_newfixed.setOnClickListener(new View.OnClickListener(){
@Override
public void onClick(View v) {
for (int i = 0; i < 30; i++){
final int curIndex = i;
Runnable runnable = new Runnable() {
@Override
public void run() {
try {
Thread.sleep(1000);
Log.d("ThreadPoolActivity", "thread run: " + curIndex);
Log.d("ThreadPoolActivity", Thread.currentThread().getName());
// ToastManager.showMsg(ThreadPoolActivity.this, Thread.currentThread().getName());
} catch (InterruptedException e) {
e.printStackTrace();
}
}
};
newFixedThreadPool.execute(runnable);
}
}
});
}
}
5)newCachedThreadPool() (按需要创建)
public static ExecutorService newCachedThreadPool() {
return new ThreadPoolExecutor(0, Integer.MAX_VALUE,
60L, TimeUnit.SECONDS,
new SynchronousQueue<Runnable>());
}
特点:
根据程序的运行情况自动来调整线程池中的线程数量,没有核心线程,它的最大线程数却为Integer.MAX_VALUE,只有非核心线程,并且每个非核心线程空闲等待的时间为60s
由于最大线程数为无限大,所以每当我们添加一个新任务进来的时候,如果线程池中有空闲的线程,则由该空闲的线程执行新任务,如果没有空闲线程,则创建新线程来执行任务。根据CachedThreadPool的特点,我们可以在有大量任务请求的时候使用CachedThreadPool,因为当CachedThreadPool中没有新任务的时候,它里边所有的线程都会因为超时而被终止。
比较适合执行大量的耗时较少的任务。
ExecutorService cachThreadPool = Executors.newCachedThreadPool();
btn_cachedthreadpool.setOnClickListener(new View.OnClickListener(){
@Override
public void onClick(View v) {
for (int i = 0; i < 30; i++){
final int curIndex = i;
Runnable runnable = new Runnable() {
@Override
public void run() {
try {
Thread.sleep(1000);
Log.d("ThreadPoolActivity " + curIndex, Thread.currentThread().getName());
} catch (InterruptedException e) {
e.printStackTrace();
}
}
};
cachThreadPool.execute(runnable);
}
}
});
过 1s 后直接执行- 打印30个任务
6) newScheduledThreadPool() 定时定期执行任务功能的线程池
public ScheduledThreadPoolExecutor(int corePoolSize) {
super(corePoolSize, Integer.MAX_VALUE,
DEFAULT_KEEPALIVE_MILLIS, MILLISECONDS,
new DelayedWorkQueue());
}
特点:
核心线程数量是固定的(我们在构造的时候传入的),但是非核心线程是无穷大,当非核心线程闲置时,则会被立即回收。
使用方法:
6-1)延迟启动任务:
public ScheduledFuture<?> schedule(Runnable command,
long delay, TimeUnit unit);
使用方法:
延迟 1 s 后执行
ScheduledExecutorService scheduledExecutorService = Executors.newScheduledThreadPool(3);
Runnable runnable = new Runnable(){
@Override
public void run() {
Log.d("TAG", "run: ----");
}
};
scheduledExecutorService.schedule(runnable, 1, TimeUnit.SECONDS);
6-2)延迟定时执行任务:
public ScheduledFuture<?> scheduleAtFixedRate(Runnable command,
long initialDelay,
long period,
TimeUnit unit);
使用方法:
延迟initialDelay 1 秒后每个period 1秒执行一次任务
ScheduledExecutorService scheduledExecutorService = Executors.newScheduledThreadPool(3);
Runnable runnable = new Runnable(){
@Override
public void run() {
Log.d("TAG", "run: ----");
}
};
scheduledExecutorService.scheduleAtFixedRate(runnable, 1, 1, TimeUnit.SECONDS);
6-3)延迟定时执行任务2:
public ScheduledFuture<?> scheduleWithFixedDelay(Runnable command,
long initialDelay,
long delay,
TimeUnit unit);
使用方法:
第一次延迟initialDelay 1 秒,以后每次延迟delay 1秒执行一个任务
ScheduledExecutorService scheduledExecutorService = Executors.newScheduledThreadPool(3);
Runnable runnable = new Runnable(){
@Override
public void run() {
Log.d("TAG", "run: ----");
}
};
scheduledExecutorService.scheduleWithFixedDelay(runnable, 1, 1, TimeUnit.SECONDS);
7) newSingleThreadExecutor() 单个线程
public static ExecutorService newSingleThreadExecutor() {
return new FinalizableDelegatedExecutorService
(new ThreadPoolExecutor(1, 1,
0L, TimeUnit.MILLISECONDS,
new LinkedBlockingQueue<Runnable>()));
}
特点:
SingleThreadExecutor的核心线程数只有1,使用SingleThreadExecutor的一个最大好处就是可以避免我们去处理线程同步问题,其实如果我们把FixedThreadPool的参数传个1,效果就和SingleThreadExecutor一致了
2. IntentService 多线程服务
IntentService是Service的子类,由于Service里面不能做耗时的操作,所以Google提供了IntentService,在IntentService内维护了一个工作线程来处理耗时操作,当任务执行完后,IntentService会自动停止。另外,可以启动IntentService多次,而每一个耗时操作会以工作队列的方式在IntentService的onHandleIntent回调方法中执行,并且,每次只会执行一个工作线程,执行完第一个再执行第二个,以此类推。
继承IntentService 类
package com.example.newdemo.mythread;
import android.app.IntentService;
import android.content.Intent;
import android.os.IBinder;
import android.util.Log;
import androidx.annotation.Nullable;
public class MyIntentService extends IntentService {
public MyIntentService() {
super("");
}
@Override
public void setIntentRedelivery(boolean enabled) {
Log.d("TAG", "-----setIntentRedelivery-----");
super.setIntentRedelivery(enabled);
}
@Override
public void onCreate() {
Log.d("TAG", "-----onCreate-----");
super.onCreate();
}
@Override
public void onStart(@Nullable Intent intent, int startId) {
Log.d("TAG", "-----onStart-----");
super.onStart(intent, startId);
}
@Override
public int onStartCommand(@Nullable Intent intent, int flags, int startId) {
Log.d("TAG", "-----onStartCommand-----");
return super.onStartCommand(intent, flags, startId);
}
@Override
public void onDestroy() {
Log.d("TAG", "-----onDestroy-----");
super.onDestroy();
}
@Nullable
@Override
public IBinder onBind(Intent intent) {
Log.d("TAG", "-----onBind-----");
return super.onBind(intent);
}
@Override
protected void onHandleIntent(@Nullable Intent intent) {
System.out.println("当前工作线程: " + Thread.currentThread().getName());
String task = intent.getStringExtra("task");
System.out.println("当前任务: " + task);
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
主activity:
package com.example.newdemo.mythread;
import androidx.appcompat.app.AppCompatActivity;
import android.content.Intent;
import android.os.Bundle;
import android.view.View;
import android.widget.Button;
import com.example.newdemo.R;
import com.example.newdemo.util.ToastManager;
public class IntentServiceActivity extends AppCompatActivity {
private Button btn_intService;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_intent_service);
btn_intService = findViewById(R.id.btn_intService);
ToastManager.showMsg(this, "开始运行程序");
System.out.println("ceshi -------------");
Intent intent = new Intent(this, MyIntentService.class);
intent.putExtra("task", "播放音乐");
startService(intent);
intent.putExtra("task", "播放视频");
startService(intent);
intent.putExtra("task", "播放图片");
startService(intent);
}
}
结果中可以看出我们startService()执行了三次, onCreate()方法只执行了一次,说明只有一个Service实例, onStartCommand()和onStart()也执行了三次,关键是onHandleIntent()也执行了三次,而且这三次是串行的,也就是执行完一个再执行下一个,当最后一个任务执行完, onDestroy()便自动执行了