最近在项目中接触到了很多有关于多线程方面的东西,并且刚好前段时间看了Java并发编程实战那本说,
所以想将自己所了解到的,以及实际中碰到的问题总结一下。
打算首先介绍一下,Java多线程相关的基础,例如Thread,Runnable。虽然这个极其的基础,但是我觉得任何东西都
绕不过基础的知识。重点会在介绍线程池,包括线程池相关类的层级结构、相关参数等。
以及在Android中有那些多线程表现形式,例如:AsyncTask,HandlerThread,IntentService, AsycTaskLoader等。
之后希望能简单的表述一下多线程与Future, TimeUnit , CountDownLatch 等等相关类的关系,以及如何在子线程中如何开启子线程去请求数据,子线程与主线程如何交互数据等。
还有就是介绍多线程相关概念,锁, 可重入锁, 死锁, 如何排查死锁, 线程发布, 竞态条件, 数据竞争 等等
应该会分几篇博客来总结相关的知识。当然其中肯定会有一些错误之处,欢迎留言指出。我会及时更正。
以上相关所有知识点,有许多借鉴自其他的大神的博客,由于看了许多,没办法一一列举,在此表示感谢。许多概念相关的知识来自于《Java并发编程实战》那本书。
一、Thread 继承Thread类,重写run方法。
我觉得Thread应该是被最经常用到的。当只需要一个简单的线程,不需要管理线程时直接:
new Thread(){
@Override
public void run() {
//请求网络等
}
}.start();
一个匿名内部类就写完了,相信很多人都写过这样的代码。需要注意的是,如果在Activity中写这样的代码很容易造成内存泄露。
即当Activity结束时,线程的工作还没有做完,这就会导致该Activity不会被回收。可以考虑用静态内部类的形式代替上面的写法。
二、 Runnable 实现Runnable接口重写run()方法
public class DisableRunnable implements Runnable{
@Override
public void run() {
//请求网络等
}
}
Runnable 是执行任务的单元,需要用Thread包装一下才可以执行。
Runnable 接口在Android中的使用也非常的多.例如View.post(),Handler.post()等等。
每当我们看到这样的代码时:
new Thread(){runnable}.start();
当你考虑用更灵活的策略来执行任务时,可以考虑利用线程池代替Thread。
三、 线程池:
下面主要说一下线程池相关的总结,
线程池结构:
最顶层接口 Executor
public interface Executor {
void execute(Runnable command);
}
ExecutorService接口 继承Executor 定义了一些有关于生命周期的方法。
public interface ExecutorService extends Executor {
void shutdown();
List<Runnable> shutdownNow();
boolean isShutdown();
boolean isTerminated();
boolean awaitTermination(long timeout, TimeUnit unit)
throws InterruptedException;
<T> Future<T> submit(Callable<T> task);
<T> Future<T> submit(Runnable task, T result);
Future<?> submit(Runnable task);
<T> List<Future<T>> invokeAll(Collection<? extends Callable<T>> tasks)
throws InterruptedException;
<T> List<Future<T>> invokeAll(Collection<? extends Callable<T>> tasks,
long timeout, TimeUnit unit)
throws InterruptedException;
<T> T invokeAny(Collection<? extends Callable<T>> tasks,
long timeout, TimeUnit unit)
throws InterruptedException, ExecutionException, TimeoutException;
}
ThreadPoolExecutor 间接实现了ExecutorService 是真正做事的线程池。最常见的几种线程池都是它的实现。
ThreadPoolExecutor 常用的构造方法:
public ThreadPoolExecutor(int corePoolSize,
int maximumPoolSize,
long keepAliveTime,
TimeUnit unit,
BlockingQueue<Runnable> workQueue,
RejectedExecutionHandler handler){}
相关参数解释:
corePoolSize 核心线程池大小
maximumPoolSize 最大线程池大小
keepAliveTime 线程池中超过corePoolSize数目的空闲线程最大存活时间;可以allowCoreThreadTimeOut(true)使得核心线程有效时间
TimeUnit keepAliveTime时间单位
workQueue 阻塞任务队列
RejectedExecutionHandler 饱和策略 当提交任务数超过maxmumPoolSize+workQueue之和时,任务会交给RejectedExecutionHandler来处理
1.当线程池小于corePoolSize时,新提交任务将创建一个新线程执行任务,即使此时线程池中存在空闲线程。
2.当线程池达到corePoolSize时,新提交任务将被放入workQueue中,等待线程池中任务调度执行
3.当workQueue已满,且maximumPoolSize>corePoolSize时,新提交任务会创建新线程执行任务
4.当提交任务数超过maximumPoolSize时,新提交任务由RejectedExecutionHandler处理
5.当线程池中超过corePoolSize线程,空闲时间达到keepAliveTime时,关闭空闲线程
注意:
这里maximumPoolSize 指的是corePoolSize + 由于队列已满并且maximumPoolSize>corePoolSize时,为执行任务创建的线程数。
所以第4条应该说成,当workQueue已满,并且线程池内的线程数已经达到了maximumPoolSize最大线程数。这时,再次提交任务会交给RejectedExecutionHandler 饱和策略去处理。
四、这里BlockingQueue
可以为:SynchronousQueue,LinkedBlockingQueue,ArrayBlockingQueue 等等。
我们需要先了解这几个队列,当清楚这些队列的性质后,再去看那几个常用的线程池,将会非常容易理解。
SynchronousQueue
SynchronousQueue 首先它是无界的,也就是说它存储的能力是没有限制的。
但是它每一个put操作必须等待一个take操作。也就是说在某一次添加元素后,必须要等其他线程将它取走,否则不能添加。
LinkedBlockingQueue
LinkedBlockingQueue 无界队列,也就是说使用LinkedBlockingQueue时,当所有corePoolSize都在忙时,新任务都会在LinkedBlockingQueue中等待。
因为LinkedBlockingQueue是无界的(最大长度为Integer.MAX_VALUE,这里认为无界)。这个时候maximumPoolSize其实就无效了,因为只有当LinkedBlockingQueue已满时,才会创建新的线程执行任务。而这里LinkedBlockingQueue永远都不会满。所以maximumPoolSize也就无效了,也就是说利用这个队列构造的线程池,永远也不会创建新的线程。
ArrayBlockingQueue
ArrayBlockingQueue 有界队列 可以设置队列的大小,能够有效的防止资源消耗。但是者也就导致这种情况控制起来会相对复杂,JDK建议的几种常见的线程池都没有使用这个队列。
PriorityBlockingQueue 优先级队列
Executors 线程池工具类,用于生产线程池。
五、几类比较常见的线程池:
public static ExecutorService newFixedThreadPool(int nThreads) {
return new ThreadPoolExecutor(nThreads, nThreads,
0L, TimeUnit.MILLISECONDS,
new LinkedBlockingQueue<Runnable>());
}
解释:构建一个固定数目的线程池。corePoolSize 与 maximumPoolSize 数值相等。当线程池中没有空闲线程时,利用 LinkedBlockingQueue
保存阻塞任务,因为之前已经说过了 LinkedBlockingQueue 是无界的,也就是说这个队列永远不会满,那maximumPoolSize 其实就没有任何意义。
public static ExecutorService newSingleThreadExecutor() {
return new FinalizableDelegatedExecutorService
(new ThreadPoolExecutor(1, 1,
0L, TimeUnit.MILLISECONDS,
new LinkedBlockingQueue<Runnable>()));
}
解释:构建一个单线程(只有一个线程,单不一定是同一个线程)的线程池。corePoolSize 与 maximumPoolSize 均为1 .利用无界的LinkedBlockingQueue 作为保存任务的队列,这样就保证无论提交多少个人物,线程池只能有一个线程在运行,其余的任务均保存在 LinkedBlockingQueue 队列中。
public static ExecutorService newCachedThreadPool() {
return new ThreadPoolExecutor(0, Integer.MAX_VALUE,
60L, TimeUnit.SECONDS,
new SynchronousQueue<Runnable>());
}
解释:构建一个具有缓存功能的线程池,corePoolSize = 0, maximumPoolSize = Integer.MAX_VALUE 。队列用的是SynchronousQueue。每加入一个任务,线程池便会构建一个线程将它取出执行(如果有空闲的线程,会利用空闲的线程,而不会创建新的线程),这样,加入多少个任务便会创建相同个数的线程。所以这个线程池的 maximumPoolSize 为Integer.MAX_VALUE。这就保证了线程池的最大线程数是无界的,理论上线程池可以有任意个线程。当线程执行完任务后,超过60秒的空闲时间即被回收销毁。
六、饱和策略:
RejectedExecutionHandler 参数用来表示饱和策略。即表示当有界队列已满,并且当前线程池中的线程数已达到 maximumPoolSize ,这时再提交任务,会交给RejectedExecutionHandler 来处理。
注意:这里一定是有界队列,因为无界队列我们认为是永远也无法填满的(SynchronousQueue直接由生产者提交给工作线程),那么也就永不到饱和策略了。
public interface RejectedExecutionHandler {
void rejectedExecution(Runnable r, ThreadPoolExecutor executor);
}
RejectedExecutionHandler 本身是一个接口。在Executors中提供了几个实现类。
1)AbortPolicy:中止,executor抛出未检查RejectedExecutionException,调用者捕获这个异常,然后自己编写能满足自己需求的处理代码。
2)DiscardRunsPolicy:遗弃最旧的,选择丢弃的任务,是本应接下来就执行的任务。
3)DiscardPolicy:遗弃会默认放弃最新提交的任务(这个任务不能进入队列等待执行时)
4)CallerRunsPolicy:调用者运行,既不会丢弃哪个任务,也不会抛出任何异常,把一些任务推回到调用者那里,以此减缓新任务流。它不会在池线程中执行最新提交的任务,但它会在一个调用了execute的线程中执行。
七、线程工厂:
线程池另外还有一个参数便是:ThreadFactory。之前在介绍常用的构造方法没有说它的原因是,一般用不到。其他构造方法中都给出了默认实现:DefaultThreadFactory。
public ThreadPoolExecutor(int corePoolSize,
int maximumPoolSize,
long keepAliveTime,
TimeUnit unit,
BlockingQueue<Runnable> workQueue,
ThreadFactory threadFactory,
RejectedExecutionHandler handler) {}
public interface ThreadFactory {
Thread newThread(Runnable r);
}
ThreadFactory 在 Executors 中也提供了几个实现类,一般博主所接触过的都是 DefaultThreadFactory 。当然我们可以自己定制,
用于添加一些Log等等。之前推荐的几种线程池都有 ThreadFactory 作为参数的方法。
八、常见的线程池的使用:
自定义线程池:
private final static ExecutorService executorService = new ThreadPoolExecutor(
5,
10,
30L,
TimeUnit.SECONDS,
new LinkedBlockingQueue<Runnable>(),
new RejectedExecutionHandler() {
@Override
public void rejectedExecution(Runnable r, ThreadPoolExecutor executor) {
//DO nothing
}
});
这里我们用的是自定义的线程池。我们定义了一个核心线程池为5的线程池,线程池内最大的线程池数为10。
当然由于我们这里定义的缓存队列为LinkedBlockingQueue, 没有制定队列大小,那么其默认无Integer.MAX_VALUE。
也就是无限大,所以最大线程数没有用处。
饱和策略也是自定的,这里当达到执行饱和策略时,什么都不做,直接丢弃。
public void request() {
executorService.execute(new Runnable() {
@Override
public void run() {
try {
//请求网络等。
} catch (Exception e) {
e.printStackTrace();
}
}
});
}
利用Executors 提供的线程池:
public void request() {
ExecutorService executor = Executors.newFixedThreadPool(5);
executor.execute(new Runnable() {
@Override
public void run() {
try {
//请求网络等。
} catch (Exception e) {
e.printStackTrace();
}
}
});
}
OK, 线程池相关就介绍到这里,下一篇会介绍Future,FutureTask, Callable 等等相关知识。