文章目录
- 4.线程池
- 4.1状态介绍
- 4.2线程的状态-练习1
- 4.3线程的状态-练习2
- 4.4线程的状态-练习3
- 4.5线程池-基本原理
- 4.6线程池-Executors默认线程池
- 4.7线程池-Executors创建指定上限的线程池
- 4.8线程池-ThreadPoolExecutor
- 4.9线程池-参数详解
- 4.10线程池-非默认任务拒绝策略
- 总结
4.线程池
4.1状态介绍
当线程被创建并启动以后,它既不是一启动就进入执行状态,也不是一直处于执行状态。线程对象在不同的时期有不同的状态。那么Java中的线程存在哪几种状态呢?状态被定义在了 java.lang.Thread.State 枚举类中,State 枚举类的源码如下:
通过源码我们可以看到 Java中的线程存在六种状态,每种线程状态的含义如下:
各个状态的转换,如下图所示:
4.2线程的状态-练习1
-
目的:本案例主要演示 TIME_WAITING 的状态转换
-
需求:编写一段代码,依次显示一个线程的这些状态:NEW -> RUNNABLE -> TIME_WAITING -> RUNNABLE -> TERMINATED
为了简化我们得开发,本次我们使用匿名内部类结合lambda表达式的方式使用多线程。 -
代码实现
public class ThreadStateDemo01 {
public static void main(String[] args) throws InterruptedException {
//定义一个内部线程
Thread thread = new Thread(()->{
System.out.println("2.执行thread.start()之后,线程的状态:"+
Thread.currentThread().getState());
});
try {
//休眠100毫秒
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("4.执行Thread.sleep(long)完成之后,线程的状态:"+
Thread.currentThread().getState());
//获取start()之前的状态
System.out.println("1.通过new初始化一个线程,但是还没有start()之前,线程的状态:"+
thread.getState());
//启动线程
thread.start();
//休眠50毫秒
Thread.sleep(50);
//因为thread1需要休眠100毫秒,所以在第50毫秒,thread处于sleep状态
System.out.println("3.执行Thread.sleep(long)时,线程的状态:"+thread.getState());
//thread1和main线程主动休眠 150毫秒,所以在第150毫秒,thread早已执行完毕
Thread.sleep(100);
System.out.println("5.线程执行完毕之后,线程的状态:"+thread.getState()+"\n");
}
}
- 控制台输出
4.3线程的状态-练习2
- 目的:本案例 主要演示 WAITING 的状态转换。
- 需求:编写一段代码,依次显示一个线程的这些状态: NEW -> RUNNABLE -> WAITING -> RUNNABLE -> TERMINATED
- 代码实现
public class ThreadStateDemo02 {
public static void main(String[] args) throws InterruptedException {
//定义一个对象,用于加锁和解锁
Object obj = new Object();
//定义一个内部线程
Thread thread1 = new Thread(()->{
System.out.println("2.执行thread.start()之后,线程的状态"+
Thread.currentThread().getState());
synchronized (obj){
try {
//thread1需要休眠100毫秒
Thread.sleep(100);
//thread1100毫秒之后,通过wait()方法释放obj对象是锁
obj.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
System.out.println("4.被object.notify()方法唤醒之后,线程的状态"+
Thread.currentThread().getState());
});
//获取start()之前的状态
System.out.println("1.通过new初始化一个线程,但是还没有start()之前,线程的状态"+
thread1.getState());
//启动线程
thread1.start();
//main线程休眠 150毫秒
Thread.sleep(150);
//因为thread1在第100毫秒进入 wait 等待状态,所以第150毫秒肯定可以获取其状态
System.out.println("3.执行object.wait()时,线程的状态:"+thread1.getState());
//声明另一个线程进行解锁
new Thread(()->{
synchronized (obj){
//唤醒等待的线程
obj.notify();
}
}).start();
//main线程休眠10毫秒等待thread1线程能够苏醒
Thread.sleep(10);
//获取 thread1运行结束之后的状态
System.out.println("5.线程执行完毕之后,线程的状态:"+thread1.getState()+"\n");
}
}
- 控制台输出结果
4.4线程的状态-练习3
- 目的:本案例主要演示BLOCKED的状态转换
- 需求:编写一段代码,依次显示一个线程的这些状态: NEW -> RUNNABLE ->BLOCKED -> RUNNABLE -> TERMINATED
- 代码实现
public class ThreadStateDemo03 {
public static void main(String[] args) throws InterruptedException {
//定义一个对象,用于加锁和解锁
Object obj2 = new Object();
//定义一个线程,先抢占了 obj2 对象的锁
new Thread(()->{
synchronized (obj2){
try {
Thread.sleep(100);//第一个线程要持有锁100毫秒
obj2.wait();//然后通过 wait()方法进行等待状态,并释放obj2的对象锁
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}).start();
//定义目标线程,获取等待获取obj2的锁
Thread thread = new Thread(()->{
System.out.println("2.执行thread.start()之后,线程的状态"+
Thread.currentThread().getState());
synchronized (obj2){
try {
Thread.sleep(100);//thread3要持有对象锁100毫秒
obj2.notify();//然后通过notify()方法唤醒所有在obj2上等待的线程继续执行后续操作
} catch (InterruptedException e) {
e.printStackTrace();
}
}
System.out.println("4.阻塞结束后,线程的状态:"+Thread.currentThread().getState());
});
//获取start()之前的状态
System.out.println("1.通过new初始化一个线程,但是还没有thread.start()之前,线程的状态:"+
thread.getState());
//启动线程
thread.start();
//先等100毫秒
Thread.sleep(100);
//第一个线程释放锁至少需要100毫秒,所以在第50毫秒时,thread正在因等待obj的对象而阻塞
System.out.println("3.因为等待锁而阻塞时,线程的状态:"+thread.getState());
//再等 300 毫秒
Thread.sleep(300);
//两个线程的执行时间加上之前等待的50毫秒总共是250毫秒,所以第300毫秒,所有的线程都已经执行完毕
System.out.println("5.线程执行完毕之后,线程的状态:"+thread.getState());
}
}
- 控制台输出
4.5线程池-基本原理
-
概述:
提到池,大家应该能想到的就是水池。水池就是一个容器,在该容器中存储了很多的水。那么什么是线程池呢?线程池也是可以看做成一个池子,在该池子中存储很多个线程。 -
线程存在的意义:
系统创建一个线程的成本是比较高的,因为它涉及到与操作系统交互,当程序中需要创建大量生存期很短暂的线程时,频繁的创建和销毁线程对系统的资源消耗有可能大于业务处理对系统资源的消耗,这样就有点“舍本逐末”了。针对这一种情况,为了提高性能,我们就可以采用线程池。线程池在启动的时候,会创建大量空闲线程当我们向线程池提交任务的时候,线程池就会启动一个线程来执行该任务,等待任务执行完毕以后,线程并不会死亡,而是再次返回到线程池中称为空闲状态。等待下一次任务的执行。 -
线程池的设计思路:
1.准备一个任务容器
2.一次性启动多个(2个)消费者线程
3.刚开始任务容器是空的,所以线程都在wait
4.直到一个外部线程向这个任务容器中扔了一个“任务”,就会有一个消费者线程被唤醒。
5.这个消费者线程取出“任务”,并且执行这个任务,执行完毕之后,继续等待下一次任务的到来
4.6线程池-Executors默认线程池
- 概述:JDK 对线程池也进行了相关实现,在真正企业开发中我们也很少去自定义线程池,而是使用JDK中自带的线程池。
- 我们也可以使用 Exexutors 中所提供的静态方法来创建线程池
- static ExecutorService newCachedThreadPool() 创建一个默认的线程池
- static newFixedThreadPool(int nThreads) 创建一个指定最多线程数量的线程池
- 代码实现
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
public class MyThreadPoolDemo {
public static void main(String[] args) throws InterruptedException {
ExecutorService executorService = Executors.newCachedThreadPool();
executorService.submit(()->{
System.out.println(Thread.currentThread().getName()+"在执行了");
});
Thread.sleep(2000);
executorService.submit(()->{
System.out.println(Thread.currentThread().getName()+"在执行了");
});
executorService.shutdown();
}
}
- 控制台输出
4.7线程池-Executors创建指定上限的线程池
使用Executors中所提供的的静态方法来创建线程池
- static ExecutorService newFixedThreadPool(int nThreads) : 创建一个指定最多线程数量的线程池
- 代码实现
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
public class MyThreadPoolDemo2 {
public static void main(String[] args) {
//参数不是初始值,而是最大值,初始值为0
ExecutorService executorService = Executors.newFixedThreadPool(10);
executorService.submit(()->{
System.out.println(Thread.currentThread().getName()+"在执行了");
});
executorService.submit(()->{
System.out.println(Thread.currentThread().getName()+"在执行了");
});
executorService.shutdown();
}
}
- 控制台输出
4.8线程池-ThreadPoolExecutor
创建线程池对象:
- ThreadPoolExecutor threadPoolExecutor = new ThreadPoolExecutor(核心线程数量,最大线程数量,空闲线程最大存活时间,任务队列,创建线程工厂,任务的拒绝策略);
- 代码实现
public class MyRunnable implements Runnable {
@Override
public void run() {
System.out.println(Thread.currentThread().getName()+"在执行了");
}
}
import java.util.concurrent.ArrayBlockingQueue;
import java.util.concurrent.Executors;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
public class MyThreadPoolDemo3 {
public static void main(String[] args) {
// 参数一:核心线程数量
// 参数二:最大线程数
// 参数三:空闲线程最大存活时间
// 参数四:时间单位---TimeUnit
// 参数五:任务队列---让任务在队列中等着,等有线程空闲了,再从这个队列中获取任务并执行
// 参数六:创建线程工厂---按照默认的方式创建线程对象
// 参数七:任务的拒绝策略--1.什么时候拒绝任务 当提交的任务>池子中最大线程数量+队列容量
//2.如何拒绝
ThreadPoolExecutor pool = new ThreadPoolExecutor(
2,
5,
2,
TimeUnit.SECONDS,
new ArrayBlockingQueue<>(10),
Executors.defaultThreadFactory(),
new ThreadPoolExecutor.AbortPolicy());
pool.submit(new MyRunnable());
pool.submit(new MyRunnable());
pool.shutdown();
}
}
- 控制台输出
4.9线程池-参数详解
4.10线程池-非默认任务拒绝策略
- RejectedExecutionHandler是jdk提供的一个任务拒绝策略接口,它下面存在4个子类。
注:明确线程池对多可执行的任务数 = 队列容量 + 最大线程数 - 案例演示1:演示ThreadPoolExecutor.AbortPolicy任务处理策略
import java.util.concurrent.ArrayBlockingQueue;
import java.util.concurrent.Executors;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
public class MyThreadPoolDemo4 {
public static void main(String[] args) {
// 参数一:核心线程数量
// 参数二:最大线程数
// 参数三:空闲线程最大存活时间
// 参数四:时间单位---TimeUnit
// 参数五:任务队列---让任务在队列中等着,等有线程空闲了,再从这个队列中获取任务并执行
// 参数六:创建线程工厂---按照默认的方式创建线程对象
// 参数七:任务的拒绝策略--1.什么时候拒绝任务 当提交的任务>池子中最大线程数量+队列容量
//2.如何拒绝
// 核心线程数量为1 , 最大线程池数量为3, 任务容器的容量为1 ,空闲线程的最大存在时间为20s
ThreadPoolExecutor pool = new ThreadPoolExecutor(
1,
3,
20,
TimeUnit.SECONDS,
new ArrayBlockingQueue<>(1),
Executors.defaultThreadFactory(),
new ThreadPoolExecutor.AbortPolicy());
// 提交5个任务,而该线程池最多可以处理4个任务,当我们使用AbortPolicy这个任务处理策略的时候,就会抛出异常
for (int i= 0;i < 5;i++) {
pool.submit(()->{
System.out.println(Thread.currentThread().getName() + "---->> 执行了任务");
});
}
pool.shutdown();
}
}
-
控制台输出
- 控制台报错,仅仅执行了4个任务,有一个任务被丢弃了。
-
案例演示2:演示ThreadPoolExecutor.DiscardPolic任务处理策略
import java.util.concurrent.ArrayBlockingQueue;
import java.util.concurrent.Executors;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
public class MyThreadPoolDemo6 {
public static void main(String[] args) {
// 参数一:核心线程数量
// 参数二:最大线程数
// 参数三:空闲线程最大存活时间
// 参数四:时间单位---TimeUnit
// 参数五:任务队列---让任务在队列中等着,等有线程空闲了,再从这个队列中获取任务并执行
// 参数六:创建线程工厂---按照默认的方式创建线程对象
// 参数七:任务的拒绝策略--1.什么时候拒绝任务 当提交的任务>池子中最大线程数量+队列容量
//2.如何拒绝
/**
* 核心线程数量为1 , 最大线程池数量为3, 任务容器的容量为1 ,空闲线程的最大存在时间为20s
*/
ThreadPoolExecutor pool = new ThreadPoolExecutor(
1,
3,
20,
TimeUnit.SECONDS,
new ArrayBlockingQueue<>(1),
Executors.defaultThreadFactory(),
new ThreadPoolExecutor.DiscardPolicy());
// 提交5个任务,而该线程池最多可以处理4个任务,当我们使用DiscardPolicy这个任务处理策略的时候,控制台不会报错
for (int i= 0;i < 5;i++) {
int y = i;
pool.submit(()->{
System.out.println(Thread.currentThread().getName()+"在执行了"+"----"+ y);
});
}
pool.shutdown();
}
}
-
控制台输出
- 控制台没有报错,仅仅执行了4个任务,有一个任务被丢弃了。
-案例演示3:演示ThreadPoolExecutor.DiscardOldestPolicy任务处理策略
- 控制台没有报错,仅仅执行了4个任务,有一个任务被丢弃了。
import java.util.concurrent.ArrayBlockingQueue;
import java.util.concurrent.Executors;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
public class MyThreadPoolDemo5 {
public static void main(String[] args) {
// 参数一:核心线程数量
// 参数二:最大线程数
// 参数三:空闲线程最大存活时间
// 参数四:时间单位---TimeUnit
// 参数五:任务队列---让任务在队列中等着,等有线程空闲了,再从这个队列中获取任务并执行
// 参数六:创建线程工厂---按照默认的方式创建线程对象
// 参数七:任务的拒绝策略--1.什么时候拒绝任务 当提交的任务>池子中最大线程数量+队列容量
//2.如何拒绝
/**
* 核心线程数量为1 , 最大线程池数量为3, 任务容器的容量为1 ,空闲线程的最大存在时间为20s
*/
ThreadPoolExecutor pool = new ThreadPoolExecutor(
1,
3,
20,
TimeUnit.SECONDS,
new ArrayBlockingQueue<>(1),
Executors.defaultThreadFactory(),
new ThreadPoolExecutor.DiscardOldestPolicy());
for (int i= 0;i < 5;i++) {
// 定义一个变量,来指定指定当前执行的任务;这个变量需要被final修饰
final int y = i;
pool.submit(()->{
System.out.println(Thread.currentThread().getName() + "---->> 执行了任务" + y);
});
}
pool.shutdown();
}
}
-
控制台输出
- 由于任务1在线程池中等待时间最长,因此任务1被丢弃。
案例演示4:演示ThreadPoolExecutor.CallerRunsPolicy任务处理策略
- 由于任务1在线程池中等待时间最长,因此任务1被丢弃。
import java.util.concurrent.ArrayBlockingQueue;
import java.util.concurrent.Executors;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
public class MyThreadPoolDemo7 {
public static void main(String[] args) {
// 参数一:核心线程数量
// 参数二:最大线程数
// 参数三:空闲线程最大存活时间
// 参数四:时间单位---TimeUnit
// 参数五:任务队列---让任务在队列中等着,等有线程空闲了,再从这个队列中获取任务并执行
// 参数六:创建线程工厂---按照默认的方式创建线程对象
// 参数七:任务的拒绝策略--1.什么时候拒绝任务 当提交的任务>池子中最大线程数量+队列容量
//2.如何拒绝
/**
* 核心线程数量为1 , 最大线程池数量为3, 任务容器的容量为1 ,空闲线程的最大存在时间为20s
*/
ThreadPoolExecutor pool = new ThreadPoolExecutor(
1,
3,
20,
TimeUnit.SECONDS,
new ArrayBlockingQueue<>(1),
Executors.defaultThreadFactory(),
new ThreadPoolExecutor.CallerRunsPolicy());
// 提交5个任务
for (int i= 0;i < 5;i++) {
pool.submit(()->{
System.out.println(Thread.currentThread().getName() + "---->> 执行了任务");
});
}
pool.shutdown();
}
}
-
控制台输出
- 通过控制台的输出,我们可以看到次策略没有通过线程池中的线程执行任务,而是直接调用任务的run()方法绕过线程池直接执行。
总结
本文从线程的六种状态切入,用代码实现了六种状态的转换并清晰的反映在控制台,然后详细的介绍了线程池的基本原理,并介绍了默认的Executors线程池,以及ThreadPoolExecutor线程池,最后还介绍了ThreadPoolExecutor线程池的各个参数以及其他的非默认任务拒绝策略。
更新不易,希望大家多多支持!!