共计 4303 个字符,预计需要花费 11 分钟才能阅读完成。
今天开始来学习一下有关 Java 多线程的知识,主要有以下知识点:
进程与线程
线程的生命周期
中断线程
线程池
进程与线程
什么是进程?
进程就是在运行过程中的程序,就好像手机运行中的微信,QQ,这些就叫做进程。
什么是线程?
线程就是进程的执行单元,就好像一个音乐软件可以听音乐,下载音乐,这些任务都是由线程来完成的。
进程与线程的关系
一个进程可以拥有多个线程,一个线程必须要有一个父进程。
线程之间共享父进程的共享资源,相互之间协同完成进程所要完成的任务。
一个线程可以创建和撤销另一个线程,同一个进程的多个线程之间可以并发执行。
线程的生命周期
新建(New)
当线程实例被 new 出来之后,调用 start()方法之前,线程处于新建状态。
可运行(Runnable)
当线程实例调用 start()方法之后,线程调度器分配处理器资源之前,线程处于可运行状态
或者线程调度器分配处理器资源给线程之后,线程处于运行中状态,这两种情况都属于可运行状态。
等待(Waitting)
当线程处于运行状态时,线程执行了 obj.wait()或 Thread.join()方法、LockSupport.park()
以及 Thread.sleep()时,线程处于等待状态。
超时等待(Timed Waitting)
当线程处于运行状态时,线程执行了 obj.wait(long)、Thread.join(long)、LockSupport.parkNanos、
LockSupport.parkUntil 以及 Thread.sleep(long)方法时,线程处于超时等待状态。
阻塞(Blocked)
当线程处于运行状态时,获取锁失败,线程进入等待队列,同时状态变为阻塞。
终止(Terminated)
当线程执行完毕或出现异常提前结束时,线程进入终止状态
注意:在任何给定时刻,一个可运行的线程可能正在运行也可能没有运行(这就是为什么将这个状态称为可运行而不是运行)。
中断线程
interrupt
方法可以用来请求终止线程。
当对一个线程调用 interrupt 方法时,线程的中断状态将被置位为 true。这是每一个线程都具有的 boolean 标志。
每个线程都应该不时地检查这个标志,已判断线程是否被中断。
源码解析
public void interrupt() {if (this != Thread.currentThread())
checkAccess();// 检查权限
synchronized (blockerLock) {
// 判断线程是否被阻塞
Interruptible b = blocker;
if (b != null) {interrupt0();
b.interrupt(this);// 如果是阻塞线程,则把中断状态设为 false 后返回
return;
}
}
interrupt0();// 否则把中断状态设为 true}
如果线程被阻塞,就无法检测中断状态。
当在一个被阻塞的线程(调用 sleep 或 wait)上调用 interrupt 方法时,阻塞调用将会被 Interrupt Exception 异常中断。
如果阻塞线程调用了 interrupt()方法,那么会抛出异常,设置标志位为 false,同时该线程会退出阻塞的。(利用这个特性可以打破死锁)
interrupted 和 isInterrupted 区别:
-
interrupted
方法是一个静态方法,它检测当前的线程是否被中断。调用 interrupted 方法会清除该线程的中断状态。 -
isInterrupted
方法是一个实例方法,可用来检测是否有线程被中断。调用这个方法不会改变中断状态。
线程池
概述
当需要执行的任务增多时,单个线程是满足不了需求的,此时就需要创建多个线程来完成需要。
多线程的最大好处就在于提高 CPU 的利用率和提高执行效率,同时也存在着一些弊端:频繁的创建和销毁线程会产生很多的性能开销。
为了解决这个问题,线程池孕育而生。
- 复用线程池中的线程,减少创建和销毁线程的性能开销
- 控制线程的并发数,避免对资源竞争而导致阻塞现象
- 更好地管理线。
ExecutorService
在 Java 中,线程池的代码起源之 Executor(翻译过来就是执行者)注意: 这是一个接口。
Executor 有一个 ExecutorService 子接口
。 实际上,一般说线程池接口,基本上说的是这个 ExecutorService。
ExecutorService 接口的默认实现类为ThreadPoolExecutor
(翻译过来就是线程池执行者)。
ThreadPoolExecutor
翻译过来就是线程池执行器,它是线程池的真正实现,构造方法提供了一些参数来配置线程池。
public ThreadPoolExecutor(int corePoolSize,
int maximumPoolSize,
long keepAliveTime,
TimeUnit unit,
BlockingQueue<Runnable> workQueue,
ThreadFactory threadFactory)
参数详解
corePoolSize
: 线程池中核心的线程数。
核心线程默认情况下会一直存活在线程池中, 即使这个核心线程啥也不干 (闲置状态)。
如果指定 ThreadPoolExecutor 的 allowCoreThreadTimeOut 这个属性为 true,那么核心线程如果不干活 (闲置状态) 的话,超过一定时间(keepAliveTime),就会被销毁掉。
maximumPollize
: 线程池所能容纳的最大线程数。超过限制,新线程会被阻塞。
keepAliveTime
: 一个非核心线程,如果不干活 (闲置状态) 的时长,超过这个参数所设定的时长, 就会被销毁掉。但是,如果设置了 allowCoreThreadTimeOut = true, 则会作用于核心线程。
unit
: 超时等待时间单位。
workQueue
: 线程池中的任务队列。每次执行 execute()会把 runnable 对象存储在这个队列中。如果队列满了,则新建非核心线程执行任务。
threadFactory
: 线程工厂,为线程池提供创建新线程的功能。
执行任务
ThreadPoolExecutor poolExecutor;
// 初始化一个线程池
poolExecutor = new ThreadPoolExecutor(corePoolSize: 3,
maximumPoolSize: 5,
keepAliveTime: 30,
TimeUnit.SECONDS,
new ArrayBlockingQueue<Runnable>(capacity: 2));
// 向线程池中添加任务
poolExecutor.execute(new Runnable() {public void run() {});
首先我们初始化一个线程池后,即可调用 execute
这个方法,里面传入 Runnable 即可向线程池添加任务。
问题又来了,既然线程池新添加了任务,那么线程池是如何处理这些批量任务?
- 如果线程数量未达到 corePoolSize,则新建一个核心线程执行任务。
- 如果线程数量达到了 corePoolSize,则将任务移入队列等待执行。
- 如果队列已满,新建线程 (非核心线程) 执行任务。
- 如果队列已满,总线程数又达到了 maximumPoolSize,就会由 RejectedExecutionHandler 抛出异常。
常用的线程池
Executors 提供了创建常用线程池的静态方法,接下也会大概讲解一下常用的四种线程池:
- 定长线程池(FixedThreadPool)
- 单线程化线程池(SingleThreadExecutor)
- 定时线程池(ScheduledThreadPool)
- 缓存线程池(CachedThreadPool)
FixedThreadPool
创建一个定长线程池,可控制线程最大并发数,超出的线程会在队列中等待。
public static ExecutorService newFixedThreadPool(int nThreads){
return new ThreadPoolExecutor(nThreads, nThreads
keepAliveTime: OL, TimeUnit.MILLISECONDS,
new LinkedBlockingQueue<Runnable>());
}
特点:
- 线程数量固定
- 只有核心线程,并且不会被回收
- 超过 corePoolSize 的线程,他们会在队列中等待,直到有一个线程可用。
- 适用于控制线程的最大并发数
CachedThreadPool
创建一个可缓存线程池,如果线程池长度超过处理需要,可灵活回收空闲线程,若无可回收,则新建线程。
public static ExecutorService newCachedThreadPool() {
return new ThneadPoolExecutor(corePoolSize: 0, maximumPoolSize: Integer.MAX VALUE,
keepAliveTime: 60L, TimeUnit. SECONDS,
new SynchronousQueue<Runnable>();}
特点:
- 无核心线程
- 非核心线程数量无限制
- 对于空闲线程回收灵活 // 超过 60s 没有使用则进行回收
- 适用于大量且耗时少的任务
ScheduledThreadPool
创建一个定长任务线程池,支持定时及周期性任务执行。
public static ScheduledExecutorService newScheduledThreadPool(int corePoolSize) {return new ScheduledThreadPoolExecutor(corePoolSize);
}
特点:
- 线程数量固定
- 非核心数量无限制
- 适用于定时或者周期性任务
SingleThreadExecutor
创建一个单线程的线程池,它只会用唯一的工作线程来执行任务,保证所有任务按照指定顺序 (FIFO, LIFO, 优先级) 执行。
public static ExecutorService newSingleThreadExecutor() {
return new FinalizableDelegatedExecutorService(new ThreadPoolExecutor(1, 1,
keepAliveTime: 0L,
TimeUnit.MILLISECONDS,
new LinkedBlockingQueue<Runnable>()));
}
特点:
- 只有一个核心线程
- 任务队列无限制
- 不需要考虑线程同步问题
- 适用于一些因为并发而导致问题的操作
总结
有关多线程暂且说到这里,先对多线程有个初步的认识,后面会深入研究多线程。
参考:Java 线程池