1、线程的生命周期
2、线程的创建方式
方式1:继承 Thread 类
步骤:
1、创建一个继承于Thread
类的子类;
2、重写Thread
类的run()
,将此线程执行的操作声明在run()
中;
3、创建Thread
类的子类的对象;
4、通过此对象调用start()
。
注意点:
- 我们不能通过直接调用
run()
的方式启动线程。 - 如果再启动一个线程,必须重新创建一个
Thread
子类的对象,调用此对象的start()
,不可以还让已经start()
的线程去执行,会报IllegalThreadStateException
。
【示例代码】
// 1. 创建一个继承于Thread类的子类
class MyThread1 extends Thread {
// 2. 重写Thread类的run()
@Override
public void run() {
for (int i = 0; i <= 100; i++) {
if (i % 2 == 0) {
System.out.println(Thread.currentThread().getName() + ": " + i);
}
}
}
}
public class ThreadTest1 {
public static void main(String[] args) {
// 3. 创建Thread类的子类的对象
MyThread1 t1 = new MyThread1();
t1.setName("线程1");
// 4.通过此对象调用start():
// ①启动当前线程 ② 调用当前线程的run()
t1.start();
// 再启动一个线程
MyThread1 t2 = new MyThread1();
t2.setName("线程2");
t2.start();
}
}
方式2:实现 Runnable 接口
创建步骤
1、创建一个实现了 Runnable
接口的类;
2、实现类去实现 Runnable
中的抽象方法:run()
;
3、创建实现类的对象;
4、将此对象作为参数传递到 Thread
类的构造器中,创建 Thread
类的对象;
5、通过 Thread
类的对象调用 start()
。
【示例代码】
// 1. 创建一个实现了Runnable接口的类
class MyThread implements Runnable {
// 2. 实现类去实现Runnable中的抽象方法:run()
@Override
public void run() {
for (int i = 0; i < 100; i++) {
if (i % 2 == 0) {
System.out.println(Thread.currentThread().getName() + ": " +i);
}
}
}
}
public class ThreadTest {
public static void main(String[] args) {
// 3. 创建实现类的对象
MyThread myThread = new MyThread();
// 4. 将此对象作为参数传递到Thread类的构造器中,创建Thread类的对象
Thread t1 = new Thread(myThread);
t1.setName("线程1");
// 5. 通过Thread类的对象调用start():
// ① 启动线程 ②调用当前线程的run()-->调用了Runnable类型的target的run()
t1.start();
// 再启动一个线程:
Thread t2 = new Thread(myThread);
t2.setName("线程2");
t2.start();
}
}
继承方式与实现方式的比较:
- 开发中优先选择实现
Runnable
接口的方式。原因:- 实现的方式没有类的单继承性的局限性。
- 实现的方式更适合来处理多个线程有共享数据的情况。
- 联系:
public class Thread implements Runnable
- 相同点:两种方式都需要重写
run()
,将线程要执行的逻辑声明在run()
中。
方式3:实现 Callable 接口
创建步骤
1、创建一个实现Callable
接口的实现类;
2、实现call()
方法,将此线程需要执行的操作声明在call()
中;
3、创建Callable
接口实现类的对象,并将此Calllable
接口实现类的对象作为参数传递到FutureTask
构造器中,创建FutureTask
对象;
4、将FutureTask
的对象作为参数传递到Thread
类的构造器中,创建Thread
对象,并调用start()
方法;
5、获取Callable
中call()
方法的返回值,get()
方法的返回值即为FutureTask
构造器参数Callable
实现类重写的call()
方法的返回值。
如何理解实现**Callable**
接口的方式创建多线程比实现**Runnable**
接口创建多线程方式强大?
1、call()
可以有返回值的。
2、call()
可以抛出异常,被外面的操作捕获,获取异常的信息。
3、Callable
是支持泛型的。
【示例代码】
// 1.创建一个实现Callable的实现类
class NumThread implements Callable<Integer> {
// 2.实现call方法,将此线程需要执行的操作声明在call()中
@Override
public Integer call() throws Exception {
int sum = 0;
for (int i = 0; i <=100; i++) {
if (i % 2 == 0) {
System.out.println(i);
sum += i;
}
}
return sum;
}
}
public class ThreadNew {
public static void main(String[] args) {
// 3.创建Callable接口实现类的对象
NumThread numThread = new NumThread();
// 4.将此Callable接口实现类的对象作为传递到FutureTask构造器中,创建FutureTask的对象
FutureTask<Integer> futureTask = new FutureTask<Integer>(numThread);
// 5.将FutureTask的对象作为参数传递到Thread类的构造器中,创建Thread对象,并调用start()
new Thread(futureTask).start();
try {
// 6.获取Callable中call方法的返回值
// get()返回值即为FutureTask构造器参数Callable实现类重写的call()的返回值。
Integer sum = futureTask.get();
System.out.println("总和为:" + sum);
} catch (InterruptedException e) {
e.printStackTrace();
} catch (ExecutionException e) {
e.printStackTrace();
}
}
}
方式4:使用线程池
1、创建线程池的四种方法
如果并发的线程数量很多,并且每个线程都是执行一个时间很短的任务就结束了,这样频繁创建线程 就会大大降低 系统的效率,因为频繁创建线程和销毁线程需要时间. 线程池就是一个容纳多个线程的容 器,池中的线程可以反复使用,省去了频繁创建线程对象的操作,节省了大量的时间和资源。
使用线程池的好处:降低资源消耗,提高响应速度,提高线程的可管理性。
Java通过Executors提供四种线程池,分别为:
- newCachedThreadPool创建一个可缓存线程池,如果线程池长度超过处理需要,可灵活回收空闲线程,若无可回收,则新建线程。
- newFixedThreadPool 创建一个定长线程池,可控制线程最大并发数,超出的线程会在队列中等待。
- newScheduledThreadPool 创建一个定长线程池,支持定时及周期性任务执行。
- newSingleThreadExecutor 创建一个单线程化的线程池,它只会用唯一的工作线程来执行任务,保证所有任务按照指定顺序(FIFO, LIFO, 优先级)执行。
2、创建步骤
1、提供指定线程数量的线程池;
2、设置线程池的属性;
3、执行指定的线程的操作。需要提供实现Runnable
接口或Callable
接口实现类的对象;
4、关闭连接池。
3、【示例代码】
class NumberThread1 implements Runnable {
@Override
public void run() {
for (int i = 0; i <= 100; i++) {
if (i % 2 == 0) {
System.out.println(Thread.currentThread().getName() + ": " + i);
}
}
}
}
class NumberThread2 implements Runnable {
@Override
public void run() {
for (int i = 0; i <= 100; i++) {
if (i % 2 != 0) {
System.out.println(Thread.currentThread().getName() + ": " + i);
}
}
}
}
public class ThreadPoolTest {
public static void main(String[] args) {
//1.提供指定线程数量的线程池
ExecutorService service = Executors.newFixedThreadPool(10);
ThreadPoolExecutor service1 = (ThreadPoolExecutor) service;
//设置线程池的属性
// System.out.println(service.getClass());
// service1.setCorePoolSize(15);
// service1.setKeepAliveTime();
//2.执行指定的线程的操作。需要提供实现Runnable接口或Callable接口实现类的对象
service.execute(new NumberThread1());
service.execute(new NumberThread2());
//3.关闭连接池
service.shutdown();
3、线程池的作用
线程池是一种基于池化思想管理线程的工具
- 降低资源消耗,提高线程利用率,降低创建和销毁线程的消耗
- 提高响应速度,任务来了,直接有线程可用可执行,而不是先创建线程,在执行
提高线程的可管理性,线程是稀缺资源,使用线程池可统一分配调度监控。
线程池解决的问题
线程池解决的核心问题就是资源管理问题,在并发环境下,系统不能确定在任意时刻中,有多少任务需要执行,就有多少资源投入。为解决资源分配问题,线程池采用了“池化思想”,是为了最大化收益,最小风险,而将资源统一在一起管理的一种思想。常见的有:
内存池:预先申请内存,提升申请内存速度,减少内存碎片。
- 连接池:预先申请数据库连接,提升申请连接的速度,降低系统的开销
- 实例池:循环使用对象,减少资源在初始化和释放时的昂贵损坏。
4、线程池参数解释
```java public ThreadPoolExecutor(int corePoolSize,
this(corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue,int maximumPoolSize,
long keepAliveTime,
TimeUnit unit,
BlockingQueue<Runnable> workQueue) {
}Executors.defaultThreadFactory(), defaultHandler);
public ThreadPoolExecutor(int corePoolSize,
int maximumPoolSize,
long keepAliveTime,
TimeUnit unit,
BlockingQueue
- **corePoolSize** - 核心线程数,正常情况下创建工作的线程数,这些线程创建后并不会消除,而是一种常驻线程
- **maximumPoolSize** - 最大线程数,与核心线程数对应,表示最大允许被创建的线程数,当任务很多,核心线程数不够用,队列已满,此时就会创建新的线程。
为什么是先添加队列,在创建最大线程?<br />在创建线程的时候是需要获取全局锁的,这个时候其他就是阻塞,影响整体效率
- **keepAliveTime** - 表示超出核心线程数之外的线程的空闲存活时间,核心线程不会消除,但是超出核心线程数的部分线程如果空闲一定时间则会被消除。
- **workQueue** - 用来存放待执行任务的队列,默认为BlockingQueue(阻塞队列)。
为什么使用阻塞队列:<br />一般队列只是一个长度有限的缓冲区,若超出长度,就无法保留当前任务,阻塞队列通过阻塞可保留当前想要继续入队的任务<br />阻塞队列可以保证队列中没有任务的时候,阻塞获取任务的线程进入wait状态,释放cpu资源。<br />阻塞队列自带阻塞和唤醒功能,不需要额外处理,无任务时,线程池利用阻塞队列的take方法挂起,从而维持核心线程存 货,不至于一直占用cpu资源
- **ThreadFactory** - 线程工厂,用来生产线程,我们可以选择默认工厂,产生的线程都在同一个组内,拥有相同优先级,且都不是守护线程。
- **RejectedExecutionHandler **- 任务拒绝策略,第一种:当我们调用shutdown等方法关闭线程池后,这时候再向线程池提交任务就会遭到拒绝;另一种情况,当达到最大线程数,线程池已经没有能力继续处理新提交的任务时,也拒绝。
<a name="aFzLi"></a>
### 拒绝策略
| 拒绝策略 | 说明 |
| --- | --- |
| AbortPolicy | 默认策略,不执行此任务,直接抛出异常 |
| DiscardOldestPolicy | 丢弃队列最前面的任务,然后重新执行此任务 |
| DiscardPolicy | 丢弃任务但不抛出异常 |
| CallerRunsPolicy | 由调度线程处理该任务 |
| 自定义策略 | 自定义策略 |
<a name="RoxJn"></a>
## 5、线程池流程处理
![image.png](https://cdn.nlark.com/yuque/0/2021/png/1161651/1616124536923-2498f3f0-3b27-42a0-95f4-fa5b202817e6.png#crop=0&crop=0&crop=1&crop=1&height=658&id=b5asI&margin=%5Bobject%20Object%5D&name=image.png&originHeight=715&originWidth=488&originalType=binary&ratio=1&rotation=0&showTitle=false&size=54492&status=done&style=none&title=&width=449)
- 将任务提交给线程池
- 如果线程池中线程数小于核心线程数,则创建一个新的线程来执行该任务
- 提交任务时,线程池中的空闲的线程数为0并且线程数等于核心线程数,则观察线程池中的任务队列是否已满,如果未满则将任务添加到任务队列
- 如果最大线程数大于核心线程数,并且总线程数小于最大线程数,则创建一个新的线程来执行该任务。
- 当任务队列已满时,就执行拒绝策略
<a name="dMM6N"></a>
## 6、线程池的核心设计与实现
<a name="KyFu0"></a>
### 设计
![image.png](https://cdn.nlark.com/yuque/0/2021/png/1161651/1616640762999-c561f642-e6e1-4b1b-8c08-d25c7634d0fd.png#crop=0&crop=0&crop=1&crop=1&height=314&id=HYMab&margin=%5Bobject%20Object%5D&name=image.png&originHeight=314&originWidth=248&originalType=binary&ratio=1&rotation=0&showTitle=false&size=9345&status=done&style=none&title=&width=248)<br />线程池的核心实现类是ThreadPoolExecutor,顶层接口是Executor, 顶层接口的设计思想是:**将任务提交和任务执行进行解耦**,用户无需关注如何创建线程,如何调度线程来执行任务。
```java
public interface Executor {
void execute(Runnable command);
}
ExecutorService接口增加了一些能力:
- 扩充执行任务的能力,补充可以为一个或一批任务生成Future方法
提供了管理线程池的方法,比如停止线程池的运行
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,
long timeout, TimeUnit unit)
throws InterruptedException;
}
AbstractExecutorService则是上层的抽象类,将执行任务的流程串联起来,保证下层的实现只需关注一个执行任务的方法即可,最下层的实现类ThreadPoolExecutor实现最复杂的运行部分,一方面维护自身的生命周期,另一方面同时管理线程和任务,使两者良好的结合从而执行并行任务。
生命周期管理
线程池内部使用一个变量维护两个值:运行状态runState和线程数量workerCount
private final AtomicInteger ctl = new AtomicInteger(ctlOf(RUNNING, 0));
ThreadPoolExecutor的运行有5种状态
运行状态 | 状态描述 |
---|---|
RUNNING | 能接受新提交的任务,并且也能处理阻塞队列中的任务 |
SHUTDOWN | 关闭状态,不再接受新提交的任务,却可以继续处理阻塞队列中已保存的任务 |
STOP | 不能接受任务,也不处理队列中的任务,会中断正在处理任务的线程 |
TIDYING | 所有的任务都已终止了,workerCount(有效线程数)为0 |
TERMINATED | 在terminated()方法执行完成后进入该状态 |
Worker
线程池为了掌握线程的状态并维护线程的生命周期,设计了线程池内的工作线程Worker
private final class Worker extends AbstractQueuedSynchronizer implements Runnable{
final Thread thread;//Worker持有的线程
Runnable firstTask;//初始化的任务,可以为null
}
线程池中线程复用的原理
线程池将线程和任务进行解耦,线程是线程,任务是任务,通过Worker实现,摆脱了之前通过thread创建线程时的一个线程必须对应一个任务的限制
在线程池中,同一个线程可以从阻塞队列中不断获取新任务来执行,其核心原理在于线程池对 Thread 进行了封装,并不是每次执行任务都会调用 Thread.start() 来创建新线程,而是让每个线程去 执行一个“循环任务”,在这个“循环任务”中不停检查是否有任务需要被执行,如果有则直接执行,也就 是调用任务中的 run 方法,将 run 方法当成一个普通的方法执行,通过这种方式只使用固定的线程就 将所有任务的 run 方法串联起来。