声明:该文章中全部测试都是在JDK1.8的环境下。html
该文章是我在学习Java中的多线程这方面知识时,作的一些总结和记录。java
若是有不正确的地方请你们多多包涵并做出指点,谢谢!编程
本文章有些内容参考并采用如下做品内容:数组
https://www.cnblogs.com/vipst...缓存
https://www.bilibili.com/vide...服务器
咱们知道CPU执行代码都是一条一条顺序执行的,因此本质上单核CPU的电脑不会在同一个时间点执行多个任务。可是在现实中,咱们在使用电脑的时候,能够一边聊微信,一边听歌。那这是怎么作到的呢?其实操做系统多任务就是让CPU对多个任务轮流交替执行。微信
举个例子:在一个教室中同时坐着一年级,二年级,三年级三批学生,老师花一分钟教一年级,花一分教二年级,花一分钟教三年级,这样轮流下去,看上去就像同时在教三个年级。多线程
一样的,咱们使用电脑时,一边聊微信,一边听歌也是这个原理。CPU让微信执行0.001秒,让音乐播放器执行0.001秒,在咱们看来,CPU就是在同时执行多个任务。异步
程序:被存储在磁盘或其余的数据存储设备中的可执行文件,也就是一堆静态的代码。ide
进程:运行在内存中可执行程序实例
线程:线程是进程的一个实体,是CPU调度和分派的基本单位。
看着这些概念是否是很抽象,看的很不舒服,那么下面我来用实例解释一下以上几个概念。
上面说到,咱们使用电脑时,能够一边聊微信,一边听歌。那这些软件运行的整个过程是怎样的呢?
若是咱们要用微信聊天,大部分的人都是双击击桌面上的"微信"快捷方式,而双击这个快捷方式打开的其实是一个.exe文件,这个.exe文件就是一个程序,请看下图:
我用一张图来总结一下整个过程:
根据上面内容对于线程概念的了解,是否有个疑问,线程是怎么建立出来的?带着这个疑问咱们就来学习一下java中的线程是怎么如何建立的。
java.lang.Thread类表明线程,任何线程都是Thread类(子类)的实例。
构造方法 | 功能介绍 |
---|---|
Thread() | 使用无参的方式构造对象 |
Thread(String name) | 根据参数指定的名称来构造对象 |
Thread(Runnable target) | 根据参数指定的引用来构造对象,其中Runnable是个接口类型 |
Thread(Runnable target, String name) | 根据参数指定引用和名称来构造对象 |
成员方法 | 功能介绍 |
---|---|
run() | 1.使用Runnable引用构造线程对象,调用该方法时最终调用接口中的版本 2.没有使用Runnable引用构造线程对象,调用该方法时则啥也不作 |
start() | 用于启动线程,Java虚拟机会自动调用该线程的run方法 |
自定义类继承Thread类并根据本身的需求重写run方法,而后在主类中建立该类的对象调用start方法,这样就启动了一个线程。
示例代码以下:
//建立一个自定义类SubThreadRun继承Thread类,做为一个能够备用的线程类 public class SubThreadRun extends Thread { @Override public void run() { //打印1~20的整数值 for (int i = 0; i < 20 ; i ++) { System.out.println("SubThreadRun线程中:" + i); } } } //在主方法中建立该线程并启动 public class SubThreadRunTest { public static void main(String[] args) { //1.申明Thread类型的引用指向子类类型的对象 Thread t1 = new SubThreadRun(); //用于启动线程,java虚拟机会自动调用该线程中的run方法 t1.start(); } } 输出结果: SubThreadRun线程中:0 SubThreadRun线程中:2 。。。。。。 SubThreadRun线程中:19
到这里你们会不会有如下一个疑问,看示例代码:
public class SubThreadRunTest { public static void main(String[] args) { //1.申明Thread类型的引用指向子类类型的对象 Thread t1 = new SubThreadRun(); //调用run方法测试 t1.run(); } } 输出结果: SubThreadRun线程中:0 SubThreadRun线程中:1 。。。。。。 SubThreadRun线程中:19
咱们不调用start方法,而是直接调用run方法,发现结果和调用start方法同样,他们两个方法的区别是啥呢?
咱们在主方法中也加入一个打印1-20的数,而后分别用run方法和start方法进行测试,实例代码以下:
//使用run方法进行测试 public class SubThreadRunTest { public static void main(String[] args) { //1.申明Thread类型的引用指向子类类型的对象 Thread t1 = new SubThreadRun(); //2.调用run方法测试 t1.run(); //打印1~20的整数值 for (int i = 0; i < 20 ; i ++) { System.out.println("-----mian-----方法中:" + i); } } } 输出结果: SubThreadRun线程中:0 SubThreadRun线程中:1 。。。。。。//都是SubThreadRun线程中 SubThreadRun线程中:19 -----mian-----方法中:0 -----mian-----方法中:1 。。。。。。//都是-----mian-----方法中 -----mian-----方法中:19 //使用start方法进行测试 public class SubThreadRunTest { public static void main(String[] args) { //1.申明Thread类型的引用指向子类类型的对象 Thread t1 = new SubThreadRun(); //用于启动线程,java虚拟机会自动调用该线程中的run方法 t1.start(); //打印1~20的整数值 for (int i = 0; i < 20 ; i ++) { System.out.println("-----mian-----方法中:" + i); } } } 输出结果: SubThreadRun线程中:0 -----mian-----方法中:0 SubThreadRun线程中:1 SubThreadRun线程中:2 -----mian-----方法中:1 。。。。。。//SubThreadRun线程和mian方法相互穿插 SubThreadRun线程中:19 -----mian-----方法中:19
从上面的例子可知:
第一种建立线程的方式咱们已经学会了,那这种建立线程的方式有没有什么缺陷呢?假如SubThreadRun类已经继承了一个父类,这个时候咱们又要把该类做为自定义线程类,若是仍是用继承Thread类的方法来实现的话就违背了Java不可多继承的概念。因此第二种建立方式就能够避免这种问题。
自定义类实现Runnable接口并重写run方法,建立该类的对象做为实参来构造Thread类型的对象,而后使用Thread类型的对象调用start方法。
示例代码以下:
//建立一个自定义类SubRunnableRun实现Runnable接口 public class SubRunnableRun implements Runnable { @Override public void run() { for (int i = 0; i < 20 ; i ++) { System.out.println("SubRunnableRun线程中:" + i); } } } //在主方法中建立该线程并启动 public class SunRunnableRunTest { public static void main(String[] args) { //1.建立自定义类型的对象 SubRunnableRun srr = new SubRunnableRun(); //2.使用该对象做为实参构造Thread类型的对象 Thread t1 = new Thread(srr); //3.使用Thread类型的对象调用start方法 t1.start(); //打印1~20之间的全部整数 for (int i = 0; i < 20 ; i ++) { System.out.println("-----mian-----方法中:" + i); } } } 输出结果: SubRunnableRun线程中:0 -----mian-----方法中:0 SubRunnableRun线程中:1 SubRunnableRun线程中:2 -----mian-----方法中:1 。。。。。。//SubRunnableRun线程和mian方法相互穿插 SubRunnableRun线程中:19 -----mian-----方法中:19
到这里你们会不会有一个疑问呢?
我在SunRunnableRunTest类的main方法中也实例化了Thread类,为何该线程调用的是实现了Runnable接口的SubRunnableRun类中的run方法,而不是Thread类中的run方法。
为了解决该疑问,咱们就进入Thread类去看一下源码,源码调用过程以下:
从上面的SunRunnableRunTest类中代码可知,咱们建立线程调用的是Thread的有参构造方法,参数是Runnable类型的。
进入到Thread类中找到该有参构造方法,看到该构造方法调用init方法,而且把target参数继续当参数传递过去。
转到对应的init方法后,发现该init方法继续调用另外一个重载的init方法,而且把target参数继续当参数传递过去。
继续进入到重载的init方法中,咱们发现,该方法中把参数中target赋值给成员变量target。
而后找到Thread类中的run方法,发现只要Thread的成员变量target存在,就调用target中的run方法。
经过查看源码,咱们能够知道为何咱们建立的Thread类调用的是Runnable类中的run方法。
上面两种建立线程的方式都须要单首创建一个类来继承Thread类或者实现Runnable接口,并重写run方法。而匿名内部类能够不建立单独的类而实现自定义线程的建立。
示例代码以下:
public class ThreadNoNameTest { public static void main(String[] args) { //匿名内部类的语法格式:父类/接口类型 引用变量 = new 父类/接口类型 {方法的重写}; //1.使用继承加匿名内部类的方式建立并启动线程 Thread t1 = new Thread() { @Override public void run() { System.out.println("继承Thread类方式建立线程..."); } }; t1.start(); //2.使用接口加匿名内部类的方式建立并启动线程 Runnable ra = new Runnable() { @Override public void run() { System.out.println("实现Runnable接口方式实现线程..."); } }; Thread t2 = new Thread(ra); t2.start(); } }
这两个利用匿名内部类建立线程的方式还能继续简化代码,尤为是使用Runnable接口建立线程的方式,可使用Lambda表达式进行简化。
示例代码以下:
public class ThreadNoNameTest { public static void main(String[] args) { //1.使用继承加匿名内部类简化后的方式建立并启动线程 new Thread() { @Override public void run() { System.out.println("简化后继承Thread类方式建立线程..."); } }.start(); //2.使用接口加匿名内部类简化后的方式建立并启动线程 new Thread(new Runnable() { @Override public void run() { System.out.println("简化后实现Runnable接口方式实现线程..."); } }).start(); //2-1.对于接口加匿名内部建立线程,能够继续使用lambda表达式进行简化。 //Java8开始支持lambda表达式:(形参列表) -> {方法体;} Runnable ra = () -> System.out.println("lambda表达式简化实现Runnable接口方式实现线程..."); new Thread(ra).start(); //继续简化 new Thread(() -> System.out.println("lambda表达式简化实现Runnable接口方式实现线程...")).start(); } }
经过上面几个例子,咱们了解了两种建立线程的方式,可是这两种方式建立线程存在一个问题,就是run方法是没有返回值的,因此若是咱们但愿在线程结束以后给出一个结果,那就须要用到实现Callable接口建立线程。
(1)Callable接口
从Java5开始新增建立线程的第三中方式为实现java.util.concurrent.Callable接口。
经常使用方法以下:
成员方法 | 功能介绍 |
---|---|
V call() | 计算结果,若是没法计算结果,则抛出一个异常 |
咱们知道启动线程只有建立一个Thread类并调用start方法,若是想让线程启动时调用到Callable接口中的call方法,就得用到FutureTask类。
(2)FutureTask类
java.util.concurrent.FutureTask类实现了RunnableFuture接口,RunnableFuture接口是Runnable和Future的综合体,做为一个Future,FutureTask能够执行异步计算,能够查看异步程序是否执行完毕,而且能够开始和取消程序,并取得程序最终的执行结果,也能够用于获取调用方法后的返回结果。
经常使用方法以下:
构造方法 | 功能介绍 |
---|---|
FutureTask(Callable<v> callable) | 建立一个FutureTask,它将在运行时执行给定的Callable |
成员方法 | 功能介绍 |
---|---|
V get() | 获取call方法计算的结果 |
从上面的概念能够了解到FutureTask类的一个构造方法是以Callable为参数的,而后FutureTask类是Runnable的子类,因此FutureTask类能够做为Thread类的参数。这样的话咱们就能够建立一个线程并调用Callable接口中的call方法。
实例代码以下:
public class ThreadCallableTest implements Callable { @Override public Object call() throws Exception { //计算1 ~ 10000之间的累加和并打印返回 int sum = 0; for (int i = 0; i <= 10000; i ++) { sum += i; } System.out.println("call方法中的结果:" + sum); return sum; } public static void main(String[] args) { ThreadCallableTest tct = new ThreadCallableTest(); FutureTask ft = new FutureTask(tct); Thread t1 = new Thread(ft); t1.start(); Object ob = null; try { ob = ft.get(); } catch (InterruptedException e) { e.printStackTrace(); } catch (ExecutionException e) { e.printStackTrace(); } System.out.println("main方法中的结果:" + ob); } } 输出结果: call方法中的结果:50005000 main方法中的结果:50005000
线程池的由来:
在讲线程池以前,先来说一个故事,一个老板开个饭店,但这个饭店很奇怪,每来一个顾客,老板就去招一个新的大厨来作菜,等这个顾客走后,老板直接把这个大厨辞了。若是是按这种经营方式的话,老板天天就忙着招大厨,啥事都干不了。
对于上面讲的这个故事,咱们现实生活中的饭店老板可没有这么蠢,他们都是在开店前就直接招了好几个大厨候在厨房,等有顾客来了,直接作菜上菜,顾客走后,厨师留在后厨待命,这样就把老板解放了。
如今咱们来说一下线程池的由来:好比说服务器编程中,若是为每个客户都分配一个新的工做线程,而且当工做线程与客户通讯结束时,这个线程被销毁,这就须要频繁的建立和销毁工做线程。若是访问服务器的客户端过多,那么会严重影响服务器的性能。
那么咱们该如何解放服务器呢?对了,就像上面讲的饭店老板同样,打造一个后厨,让厨师候着。相对于服务器来讲,就建立一个线程池,让线程候着,等待客户端的链接,等客户端结束通讯后,服务器不关闭该线程,而是返回到线程中待命。这样就解放了服务器。
线程池的概念:
首先建立一些线程,他们的集合称为线程池,当服务器接收到一个客户请求后,就从线程池中取出一个空余的线程为之服务,服务完后不关闭线程,而是将线程放回到线程池中。
相关类和方法:
Executors是一个工具类和线程池的工厂类,用于建立并返回不一样类型的线程池,经常使用的方法以下:
返回值 | 方法 | 功能介绍 |
---|---|---|
static ExecutorService | newFixedThreadPool(int nThreads) | 建立一个固定大小的线程池,若是任务数超出线程数,则超出的部分会在队列中等待 |
static ExecutorService | newCachedThreadPool() | 建立一个可已根据须要建立新线程的线程池,若是同一时间的任务数大于线程数,则能够根据任务数建立新线程,若是任务执行完成,则缓存一段时间后线程被回收。 |
static ExecutorService | newSingleThreadExecutor() | 建立单个线程数的线程池,能够保证先进先出的执行顺序 |
static ScheduledExecutorService | newScheduledThreadPool(int corePoolSize) | 建立一个能够执行延迟任务的线程池 |
static ScheduledExecutorService | newSingleThreadScheduledExecutor() | 建立一个单线程的能够执行延迟任务的线程池 |
static ExecutorService | newWorkStealingPool() | 建立一个抢占式执行的线程池(任务执行顺序不肯定)【JDK 1.8 添加】 |
ThreadPoolExecutor经过构造方法建立线程池,最多能够设置7个参数,建立线程池的构造方法以下:
构造方法 | 功能介绍 |
---|---|
ThreadPoolExecutor(int corePoolSize, int maximumPoolSize, long keepAliveTime, TimeUnit unit, BlockingQueue<Runnable> workQueue, ThreadFactory threadFactory, RejectedExecutionHandler handler) | 经过最原始的方法建立线程池 |
经过上面两类方法建立完线程池后均可以用ExecutorService接口进行接收,它是真正的线程池接口,主要实现类是ThreadPoolExecutor,经常使用方法以下:
方法声明 | 功能介绍 |
---|---|
void execute(Runnable command) | 执行任务和命令,一般用于执行Runnable |
<T> Future<T> submit(Callable<T> task) | 执行任务和命令,一般用于执行Callable |
void shutdown() | 启动有序关闭 |
代码实例:
使用newFixedThreadPool方法建立线程池
public class FixedThreadPool { public static void main(String[] args) { // 建立含有两个线程的线程池 ExecutorService threadPool = Executors.newFixedThreadPool(2); // 建立任务 Runnable runnable = new Runnable() { @Override public void run() { System.out.println("线程:" + Thread.currentThread().getName() + "执行了任务!"); } }; // 线程池执行任务 threadPool.execute(runnable); threadPool.execute(runnable); threadPool.execute(runnable); threadPool.execute(runnable); } } 输出结果: 线程:pool-1-thread-2执行了任务! 线程:pool-1-thread-1执行了任务! 线程:pool-1-thread-2执行了任务! 线程:pool-1-thread-1执行了任务!
从结果上能够看出,这四个任务分别被线程池中的固定的两个线程所执行,线程池也不会建立新的线程来执行任务。
使用newCachedThreadPool方法建立线程池
public class cachedThreadPool { public static void main(String[] args) { //1.建立线程池 ExecutorService executorService = Executors.newCachedThreadPool(); //2.设置任务 Runnable runnable = new Runnable() { @Override public void run() { System.out.println("线程:" + Thread.currentThread().getName() + "执行了任务!"); try { TimeUnit.SECONDS.sleep(1); } catch (InterruptedException e) { } } }; //3.执行任务 for (int i = 0; i < 100; i ++) { executorService.execute(runnable); } } } 输出结果: 线程:pool-1-thread-1执行了任务! 线程:pool-1-thread-4执行了任务! 线程:pool-1-thread-3执行了任务! 线程:pool-1-thread-2执行了任务! 线程:pool-1-thread-5执行了任务! 线程:pool-1-thread-7执行了任务! 线程:pool-1-thread-6执行了任务! 线程:pool-1-thread-8执行了任务! 线程:pool-1-thread-9执行了任务! 线程:pool-1-thread-10执行了任务!
从结果上能够看出,线程池根据任务的数量来建立对应的线程数量。
使用newSingleThreadExecutor的方法建立线程池
public class singleThreadExecutor { public static void main(String[] args) { //建立线程池 ExecutorService executorService = Executors.newSingleThreadExecutor(); //执行任务 for (int i = 0; i < 10; i ++) { final int task = i + 1; executorService.execute(()->{ System.out.println("线程:" + Thread.currentThread().getName() + "执行了第" + task +"任务!"); try { Thread.sleep(1000); } catch (InterruptedException e) { e.printStackTrace(); } }); } } } 输出结果: 线程:pool-1-thread-1执行了第1任务! 线程:pool-1-thread-1执行了第2任务! 线程:pool-1-thread-1执行了第3任务! 线程:pool-1-thread-1执行了第4任务! 线程:pool-1-thread-1执行了第5任务! 线程:pool-1-thread-1执行了第6任务! 线程:pool-1-thread-1执行了第7任务! 线程:pool-1-thread-1执行了第8任务! 线程:pool-1-thread-1执行了第9任务! 线程:pool-1-thread-1执行了第10任务!
从结果能够看出,该方法建立的线程能够保证任务执行的顺序。
使用newScheduledThreadPool的方法建立线程池
public class ScheduledThreadPool { public static void main(String[] args) { //建立包含2个线程的线程池 ScheduledExecutorService scheduledExecutorService = Executors.newScheduledThreadPool(2); //记录建立任务时的当前时间 SimpleDateFormat formatter = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"); Date startTime = new Date(); String start = formatter.format(startTime); System.out.println("建立任务时的时间:" + start); //建立任务 Runnable runnable = new Runnable() { @Override public void run() { Date endTime = new Date(); String end = formatter.format(endTime); System.out.println("线程:" + Thread.currentThread().getName() + "任务执行的时间为:" + end); } }; //执行任务(参数:runnable-要执行的任务,2-从如今开始延迟执行的时间,TimeUnit.SECONDS-延迟参数的时间单位) for(int i = 0; i < 2; i ++) { scheduledExecutorService.schedule(runnable,2, TimeUnit.SECONDS); } } } 输出结果: 建立任务的时间:2021-04-19 19:26:18 线程:pool-1-thread-1任务执行的时间为:2021-04-19 19:26:20 线程:pool-1-thread-2任务执行的时间为:2021-04-19 19:26:20
从结果能够看出,该方法建立的线程池能够分配已有的线程执行一些须要延迟的任务。
使用newSingleThreadScheduledExecutor方法建立线程池
public class SingleThreadScheduledExecutor { public static void main(String[] args) { //建立线程池 ScheduledExecutorService scheduledExecutorService = Executors.newSingleThreadScheduledExecutor(); //建立任务 Date startTime = new Date(); SimpleDateFormat formatter = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"); String start = formatter.format(startTime); System.out.println("建立任务的时间:" + start); Runnable runnable = new Runnable() { @Override public void run() { Date endTime = new Date(); String end = formatter.format(endTime); System.out.println("线程:" + Thread.currentThread().getName() + "任务执行的时间为:" + end); } }; //执行任务 for(int i = 0; i < 2; i ++) { scheduledExecutorService.schedule(runnable,2, TimeUnit.SECONDS); } } } 输出结果: 建立任务的时间:2021-04-19 19:51:58 线程:pool-1-thread-1任务执行的时间为:2021-04-19 19:52:00 线程:pool-1-thread-1任务执行的时间为:2021-04-19 19:52:00
从结果能够看出,该方法建立的线程池只有一个线程,该线程去执行一些须要延迟的任务。
使用newWorkStealingPool方法建立线程池
public class newWorkStealingPool { public static void main(String[] args) { //建立线程池 ExecutorService executorService = Executors.newWorkStealingPool(); //执行任务 for (int i = 0; i < 4; i ++) { final int task = i + 1; executorService.execute(()->{ System.out.println("线程:" + Thread.currentThread().getName() + "执行了第" + task +"任务!"); }); } //确保任务被执行 while (!executorService.isTerminated()) { } } } 输出结果: 线程:ForkJoinPool-1-worker-9执行了第1任务! 线程:ForkJoinPool-1-worker-4执行了第4任务! 线程:ForkJoinPool-1-worker-11执行了第3任务! 线程:ForkJoinPool-1-worker-2执行了第2任务!
从结果能够看出,该方法会建立一个含有足够多线程的线程池,来维持相应的并行级别,任务会被抢占式执行。(任务执行顺序不肯定)
使用ThreadPoolExecutor建立线程池
在编写示例代码以前我先来说一个生活的例子(去银行办理业务):
描述业务场景:银行一共有4个窗口,今天只开放两个,而后等候区一共3个位置。以下图所示:
若是银行同时办理业务的人小于等于5我的,那么正好,2我的先办理,其余的人在等候区等待。以下图所示:
若是银行同时办理业务的人等于6我的时,银行会开放三号窗口来办理业务。以下图所示:
若是银行同时办理业务的人等于7我的时,银行会开放四号窗口来办理业务。以下图所示:
若是银行同时办理业务的人大于7我的时,则银行大厅经理就会告诉后面的人,该网点业务已满,请去其余网点办理。
如今咱们再来看一下咱们的ThreadPoolExecutor构造方法,该构造方法最多能够设置7个参数:
ThreadPoolExecutor(int corePoolSize, int maximumPoolSize, long keepAliveTime, TimeUnit unit, BlockingQueue<Runnable> workQueue, ThreadFactory threadFactory, RejectedExecutionHandler handler)
参数介绍:
unit:时间单位,是第三个参数的单位,这两个参数组合成最大线程数的存活时间
workQueue:等待队列,用于保存在线程池等待的任务(对应银行办理业务模型:等待区)
handler:拒绝策略,任务超出线程池可接受范围时,拒绝处理任务时的策略。
当任务数小于等于核心线程数+等待队列数量的总和时:
public class ThreadPoolExecutorTest { public static void main(String[] args) { // 建立线程池 ThreadPoolExecutor threadPool = new ThreadPoolExecutor(2, 4, 100, TimeUnit.SECONDS, new ArrayBlockingQueue<>(3), Executors.defaultThreadFactory(),new ThreadPoolExecutor.AbortPolicy()); //建立任务 Runnable runnable = new Runnable() { @Override public void run() { System.out.println(Thread.currentThread().getName() + "==>执行任务"); } }; // 执行任务 for (int i = 0; i < 5; i++) { threadPool.execute(runnable); } //关闭线程池 threadPool.shutdown(); } } 输出结果: pool-1-thread-2==>执行任务 pool-1-thread-1==>执行任务 pool-1-thread-2==>执行任务 pool-1-thread-1==>执行任务 pool-1-thread-2==>执行任务
从结果中能够看出,只有两个核心线程在执行任务。
当任务数大于核心线程数+等待队列数量的总和,可是小于等于最大线程数时:
public class ThreadPoolExecutorTest { public static void main(String[] args) { // 建立线程池 ThreadPoolExecutor threadPool = new ThreadPoolExecutor(2, 4, 100, TimeUnit.SECONDS, new ArrayBlockingQueue<>(3), Executors.defaultThreadFactory(),new ThreadPoolExecutor.AbortPolicy()); //建立任务 Runnable runnable = new Runnable() { @Override public void run() { System.out.println(Thread.currentThread().getName() + "==>执行任务"); } }; // 执行任务 for (int i = 0; i < 7; i++) { threadPool.execute(runnable); } //关闭线程池 threadPool.shutdown(); } } 输出结果: pool-1-thread-1==>执行任务 pool-1-thread-4==>执行任务 pool-1-thread-2==>执行任务 pool-1-thread-2==>执行任务 pool-1-thread-3==>执行任务 pool-1-thread-4==>执行任务 pool-1-thread-1==>执行任务
从结果中能够看出,启动了最大线程来执行任务。
当任务数大于最大线程数时:
public class ThreadPoolExecutorTest { public static void main(String[] args) { // 建立线程池 ThreadPoolExecutor threadPool = new ThreadPoolExecutor(2, 4, 100, TimeUnit.SECONDS, new ArrayBlockingQueue<>(3), Executors.defaultThreadFactory(),new ThreadPoolExecutor.AbortPolicy()); //建立任务 Runnable runnable = new Runnable() { @Override public void run() { System.out.println(Thread.currentThread().getName() + "==>执行任务"); } }; // 执行任务 for (int i = 0; i < 8; i++) { threadPool.execute(runnable); } //关闭线程池 threadPool.shutdown(); } } 输出结果: Exception in thread "main" java.util.concurrent.RejectedExecutionException: Task com.zck.task18.ThreadPool.ThreadPoolExecutorTest$1@7f31245a rejected from java.util.concurrent.ThreadPoolExecutor@6d6f6e28[Running, pool size = 4, active threads = 0, queued tasks = 0, completed tasks = 7] at java.util.concurrent.ThreadPoolExecutor$AbortPolicy.rejectedExecution(ThreadPoolExecutor.java:2063) at java.util.concurrent.ThreadPoolExecutor.reject(ThreadPoolExecutor.java:830) at java.util.concurrent.ThreadPoolExecutor.execute(ThreadPoolExecutor.java:1379) at com.zck.task18.ThreadPool.ThreadPoolExecutorTest.main(ThreadPoolExecutorTest.java:25) pool-1-thread-1==>执行任务 pool-1-thread-4==>执行任务 pool-1-thread-4==>执行任务 pool-1-thread-4==>执行任务 pool-1-thread-2==>执行任务 pool-1-thread-3==>执行任务 pool-1-thread-1==>执行任务
从结果中能够看出,任务大于最大线程数,使用拒绝策略直接抛出异常。
本文介绍了三种线程的建立方式:
介绍了七种线程池的建立方式: