线程池
创建单线程的线程池:newSingleThreadExecutor
线程池里面只有一个线程。
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
public class TestSimpleThreadPool {
public static void main(String[] args) {
// 创建了一个线程池,里面只有一个线程
ExecutorService es =Executors.newSingleThreadExecutor();
// 循环的执行任务,好处就是从始至终只有一个线程pool-1-thread-1(线程池中的线程做了做大的复用)
for(int i=0;i<1000;i++) {
// 外部类的变量如果被匿名内部类使用,必须使用final修饰,因为匿名内部类修改了外部类的变量会造成不安全的操作
final int index=i;
// 线程池调用execute(Runnable)方法执行任务
es.execute(new Runnable() {
@Override
public void run() {
System.out.println(Thread.currentThread().getName()+":::"+index);
}
});
// RejectedExecutionException拒绝处理异常,线程池已经停止了,还在处理任务就会抛出该异常
// 不要停止线程池,或者说不要再for循环中调用shutdown()
}
// 停止线程池
es.shutdown();
}
}
创建固定数量的线程池:newFixedThreadPool(线程数量)
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
public class TestFixThreadPool {
public static void main(String[] args) {
// 创建固定数量的线程池,此时线程池中有10个线程
ExecutorService es = Executors.newFixedThreadPool(10);
// 线程只有10个,但是有100个任务,多余的任务放入队列中等线程池中的线程处理完毕再从队列中拿任务处理
for(int i=0;i<100;i++) {
final int index=i;
es.execute(()->System.out.println(Thread.currentThread().getName()+"::"+index));
}
es.shutdown();
}
}
队列中只有一个任务的线程池
由于队列中只有一个任务,每当有任务上来,线程池就会创建一个线程处理任务,10000个任务就会有10000个线程处理,内存消耗高,工作中不建议使用。
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
public class TestSimpleQueueThreadPool {
public static void main(String[] args) {
ExecutorService es = Executors.newCachedThreadPool();
for(int i=0;i<10000;i++) {
es.execute(new MyTask());
}
}
}
class MyTask implements Runnable{
@Override
public void run() {
System.out.println(Thread.currentThread().getName()+":::HelloWorld");
}
}
执行定时任务的线程池:newScheduledThreadPool(线程数量)
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
import java.util.Date;
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;
public class TestSchThreadPool {
public static void main(String[] args) {
// 创建一个线程池,里面有10个线程,执行定时任务
ScheduledExecutorService ses = Executors.newScheduledThreadPool(10);
/*// 只能执行一次
ses.schedule(new Runnable() {
@Override
public void run() {
System.out.println(new Date());
}
}, 2000, TimeUnit.MILLISECONDS);*/
// 参数1:需要放入线程池的任务。参数2:初始化延迟执行的时间 。参数3:执行的周期。 参数4:参数3的单位
// scheduleAtFixedRate()方法的执行周期不包含任务执行的时间
// 缺点:一旦执行任务的时间大于指定的周期,定时任务的执行将没有执行规律
/*ses.scheduleAtFixedRate(new Runnable() {
@Override
public void run() {
//年月日 时分秒
LocalDateTime ldt = LocalDateTime.now().withNano(0);
DateTimeFormatter dtf = DateTimeFormatter.ofPattern("yyyy-LL-dd HH:mm:ss");
String currentTime = dtf.format(ldt);
System.out.println(Thread.currentThread().getName()+"::"+currentTime);
try {
// 每次执行定时任务会休眠1500毫秒(1.5秒),每次执行周期是2秒还是3.5秒
// 结果:2秒,如果休眠时间大于周期(3.5秒),执行周期就是休眠的时间(3.5秒)
Thread.sleep(3500);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}, 1000, 2000,TimeUnit.MILLISECONDS);*/
// 指定的周期执行定时任务,包含了任务执行的时间(每次周期=任务执行时间+周期时间)
ses.scheduleWithFixedDelay(() -> {
// 年月日 时分秒
LocalDateTime ldt = LocalDateTime.now().withNano(0);
DateTimeFormatter dtf = DateTimeFormatter.ofPattern("yyyy-LL-dd HH:mm:ss");
String currentTime = dtf.format(ldt);
System.out.println(Thread.currentThread().getName() + ":::" + currentTime);
try {
// Thread.sleep(3000):模拟任务执行的需要时间,例如执行该任务需要3秒
// 每次执行的周期会在上一个周期之后的一秒钟执行
// 执行周期1000+3000=4000
Thread.sleep(3000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}, 1000, 1000, TimeUnit.MILLISECONDS);
}
}
小结:
scheduleWithFixedDelay():指定的周期执行定时任务,包含了任务执行的时间(每次周期=任务执行时间+周期时间)
scheduleAtFixedRate():的执行周期不包含任务执行的时间,例如每次间隔1000执行
线程池参数介绍
// 固定线程数量的线程池,例如:传入的参数nThreads为10
/**
参数1:核心线程大小
参数2:最大线程大小
参数3:最大线程大小在没有任务处理(处于闲置状态)的存活时间,此时默认是0L
参数4:参数3的单位
参数5:线程池的队列,此时的队列为阻塞式的无界限的队列,也就是说队列里面的任务可以是2^31-1
特征:核心线程的大小和最大线程大小是一致的,那么参数2是一个哑巴参数,参数3也是哑巴参数 参数4也是哑巴参数 (参数1和参数5是核心参数)
优点:程序在执行过程中稳定(不会出现栈内存溢出错误),因为从始至终线程池只有10个线程
缺点:执行效率相对慢(队列是一个无界限的,可以容纳N多个任务,导致很多任务在队列中等待线程执行)
*/
public static ExecutorService newFixedThreadPool(int nThreads) {
return new ThreadPoolExecutor(nThreads, nThreads,
0L, TimeUnit.MILLISECONDS,
new LinkedBlockingQueue<Runnable>());
}
/**
* 核心线程和最大线程都是1
* 工作中执行任务时间较短并且任务相对较少使用该线程池
* 安装软件会用到该线程池
*/
public static ExecutorService newSingleThreadExecutor() {
return new FinalizableDelegatedExecutorService
(new ThreadPoolExecutor(1, 1,
0L, TimeUnit.MILLISECONDS,
new LinkedBlockingQueue<Runnable>()));
}
/**
参数1:核心线程数 此时为0
参数2:最大线程数量 此时为2^31-1
参数3:最大线程数量在没有任务可以处理的情况下存活的时间,此时是60秒
参数4:参数3的单位
参数5:线程池的队列,该队列最多只能容纳一个任务
优点:同时可以使用多个线程处理任务(同时上来100个任务,会创建100个线程处理),处理的速度会很快速
缺点:一旦同一时刻任务过多(一秒钟有10000个任务会同时开启10000个线程处理),内存负载很重,会出现内存溢出错误
超过最大线程数量会抛出RejectedExecutionException拒绝处理任务的异常
例如:最大线程数量是5000同一时刻上来4900个任务,此时就会创建4900个线程处理任务,处理完毕60秒之后还没有新的任务上来,线程池就会销毁(kill)掉所有的线程(4900).频繁创建和频繁销毁线程消耗资源很大
*/
public static ExecutorService newCachedThreadPool() {
return new ThreadPoolExecutor(0,5000,
60L, TimeUnit.SECONDS,
new SynchronousQueue<Runnable>());
}
自定义线程池
场景:一个项目只有一个线程池对象或者一个项目可以有多个线程池但是每个线程池只创建一次(工作中需要复用线程池,而不是频繁创建线程池)。
问题:JDK提供的线程池不能复用
如何复用线程池?
需要自己编写线程池,只创建一次,多次调用
需要在借鉴的基础上去创新(借鉴JDK提供的线程池来创建我们自定义的线程池)
import java.util.concurrent.ExecutorService;
import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
/**
* 自定义的线程池
*/
public class J0812ThreadPool {
/**
* es 在整个项目中只初始化一次,只有一份内存空间
*/
private static ExecutorService es;
/**
* 创建线程池
* 第一次调用create()方法es对象为null,就会执行if语句创建线程池,后面再调用create()方法es不为空就不会执行
* if语句的代码,直接返回es对象
* 建议:工作中自定义线程池的线程数量不要超过500
* @return 线程池对象
*/
public static ExecutorService create() {
if(null ==es) {
//创建线程池
es=new ThreadPoolExecutor(500, 500,
0L, TimeUnit.MILLISECONDS,
new LinkedBlockingQueue<Runnable>());
}
return es;
}
}
线程池测试类
package com.caojie.day14.test;
import java.util.concurrent.ExecutorService;
import com.caojie.day14.utils.J0812ThreadPool;
public class TestThreadPool {
public static void main(String[] args) {
//循环调用J0812ThreadPool类型的create方法获取线程池,观察会创建几个线程池对象
//答案:1个线程池对象对象
// for(int i=0;i<100;i++) {
// ExecutorService es = J0812ThreadPool.create();
// System.out.println(es);
// }
ExecutorService es = J0812ThreadPool.create();
for(int i=0;i<100000;i++) {
es.execute(new Runnable() {
@Override
public void run() {
System.out.println(Thread.currentThread().getName()+"::Helloworld");
}
});
}
}
}
使用枚举创建线程池
场景:一个类只能创建一个线程池,如果你想把创建线程池的细节隐藏,还想创建多个线程池,使用枚举完成
场景:
创建两个线程池一个容纳10个线程,一个容纳500个线程
前提:需要在枚举中定义抽象方法,还需要在枚举元素中定义属性
枚举创建线程步驟:
1.创建枚举
2.定义枚举的元素
3.为元素添加属性值
4.设置枚举的成员属性,为属性添加get方法
5.定义抽象方法 public abstract ExecutorService create();
6.为每个枚举元素覆盖抽象方法
7.为每个枚举元素定义属性private ExecutorService es;
8.为每个枚举元素编写抽象方法的实现
9.定义静态方法让外界访问枚举的线程池
import java.util.concurrent.ExecutorService;
import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
public enum EJ0812ThreadPool {
/**
* @author caojie
* 一旦枚举中定义了抽象方法,每个元素必须实现该抽象方法
* 一旦枚举的每个元素覆盖了抽象方法,该元素编译期是枚举的元素,运行期是枚举里面的匿名内部类
* 枚举中的元素只初始化有一次
*
* 该元素表示:10个线程的线程池
*/
TEN_THREAD_POOL("ten") {
private ExecutorService es;
@Override
public ExecutorService create() {
if(null == es) {
es = new ThreadPoolExecutor(10, 10,
0L, TimeUnit.MILLISECONDS,
new LinkedBlockingQueue<Runnable>());
}
return es;
}
},
/**
* 该元素表示:500个线程的线程池
*/
FIVE_HUNDRED_THREAD_POOL("fiveHundred") {
private ExecutorService es;
@Override
public ExecutorService create() {
if(null == es) {
es = new ThreadPoolExecutor(500, 500,
0L, TimeUnit.MILLISECONDS,
new LinkedBlockingQueue<Runnable>());
}
return es;
}
};
private String threadPoolName;
private EJ0812ThreadPool(String threadPoolName) {
this.threadPoolName = threadPoolName;
}
public String getThreadPoolName() {
return threadPoolName;
}
public abstract ExecutorService create();
/**
* 提供给外界使用
* 外界传入对应的线程池名称,返回对应的线程池
*/
public static ExecutorService getThreadPoolByName(String name)throws Exception{
//遍历枚举中的每个元素
for(EJ0812ThreadPool pool : EJ0812ThreadPool.values()) {
//条件成立:表示外界传入的参数名称等于线程池中的元素名称,返回元素对应的线程池
if(pool.getThreadPoolName().equals(name)) {
return pool.create();
}
}
return null;
}
}
测试枚举线程池
package com.caojie.day14.test;
import java.util.concurrent.ExecutorService;
import com.caojie.day14.utils.EJ0812ThreadPool;
public class TestEnumThreadPool {
public static void main(String[] args) {
for(int i=0;i<10;i++) {
ExecutorService es;
try {
es = EJ0812ThreadPool.getThreadPoolByName("fiveHundred");
System.out.println("::"+es);
} catch (Exception e) {
e.printStackTrace();
}
}
}
}
带有返回值的任务
场景:之前我们定义的任务都是实现了Runnable接口,然后覆盖run()方法,缺点:1不能使用throws关键字声明异常,2没有返回类型
如果你想让你的任务能够有返回结果,并且能够声明异常,就需要使用带有返回值的任务
如何编写?需要实现Callable接口
语法:
//注意实现Callable接口的类全部使用Job结束
public class XXXJob implements Callable<返回类型>{
public 返回类型 call()throws Exception{
return 返回类型;
}
}
场景:使用线程池+Callable完成并发,遍历0~1000之间的整数,判断每个整数的奇偶性
call()方法 奇数返回false 偶数返回true
package com.caojie.day14.callable;
import java.util.concurrent.Callable;
/**
* @author caojie
*计算每个整数的奇偶性
*/
public class CalcOddEvenJob implements Callable<Boolean> {
private int num;
public CalcOddEvenJob(int num) {
this.num = num;
}
@Override
public Boolean call() throws Exception {
//return num%2==0;
//1000
//0001 0
//0111
//0001 1
return (num&1)==0;
}
}
package com.caojie.day14.callable;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Future;
import com.caojie.day14.utils.EJ0812ThreadPool;
/**
* @author caojie
* 使用线程池+Callable完成并发,遍历0~1000之间的整数,判断每个整数的奇偶性
call()方法: 奇数返回false,偶数返回true
*/
public class TestCallable {
public static void main(String[] args) {
try {
ExecutorService es = EJ0812ThreadPool.getThreadPoolByName("ten");
for(int i=0;i<=1000;i++) {
// 将任务提交到线程池,线程池判断有没有空闲的线程,如果有直接派发线程处理任务
// 如果线程处于忙的状态将任务put到队列
// Future:有可能立马返回结果,有可能在未来的某一时间段返回结果
// 为什么返回Future?Future因为是“未来”的意思
// Java编译期并不知道你的任务体是立马执行完毕返回结果,还是消耗很长时间返回结果
// 所以使用Future来接受返回结果
Future<Boolean> future = es.submit(new CalcOddEvenJob(i));
// 如何获取返回的结果?
// V get() throws InterruptedException, ExecutionException;
// get()方法声明了两个异常
// InterruptedException表示执行任务体的时候会中断执行,一旦中断了抛出该异常
// ExecutionException 在执行的过程中发生执行异常
boolean result = future.get();
System.out.println(i+"===="+result);
}
} catch (Exception e) {
e.printStackTrace();
}
}
}