这个博客的内容还是线程池的创建方式,话不多说,直接进入正题!!
(4). 线程池创建方式4:创建单线程执行定时任务的线程池
import java.util.Date;
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;
public class ThreadPollDemo51 {
public static void main(String[] args) {
/**
* 创建线程池方式4: 创建单个执行定时任务的线程池
* 以上一次任务结束时间作为这次任务的开始时间的!!!!!!!
* 第三种创建方式的单机版本
*/
ScheduledExecutorService service = Executors.newSingleThreadScheduledExecutor(); //创建单个线程执行定时任务
service.scheduleWithFixedDelay(new Runnable() {
@Override
public void run() {
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("执行任务:"+new Date());
}
},1,3, TimeUnit.SECONDS);
}
}
执行任务:Fri May 21 18:08:40 CST 2021
执行任务:Fri May 21 18:08:44 CST 2021
执行任务:Fri May 21 18:08:48 CST 2021
执行任务:Fri May 21 18:08:52 CST 2021
......
(5). 线程池创建方式5:创建单个线程
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
public class ThreadPollDemo52 {
public static void main(String[] args) {
//创建方式5: 创建单个线程
ExecutorService service = Executors.newSingleThreadExecutor(); //单纯创建线程池
for (int i = 0; i < 10; i++) {
service.execute(new Runnable() {
@Override
public void run() {
//如果线程的名称都是相同的,代表是单个线程的线程池
System.out.println("线程名:"+Thread.currentThread().getName());
}
});
}
}
}
线程名:pool-1-thread-1
线程名:pool-1-thread-1
线程名:pool-1-thread-1
线程名:pool-1-thread-1
线程名:pool-1-thread-1
线程名:pool-1-thread-1
线程名:pool-1-thread-1
线程名:pool-1-thread-1
线程名:pool-1-thread-1
线程名:pool-1-thread-1
经典面试题:创建单个线程池有什么作用?
- 1.可以避免频繁创建和销毁线程带来的性能开销(不释放程序计数器,本地栈等)
- 2.有任务队列可以存储多余的任务,所以可以更好的执行和管理更多的任务(先进先出的队列)
- 3.当有大量的任务不能处理的时候可以友好的执行拒绝策略
- 4.线程池可以更好的管理和分配任务(可以保证任务的顺序)
(6). 线程池创建方式6:JDK8以后才有的,根据当前的硬件CPU和任务量生成对应个数的线程池,并且是异步执行的
异步的方式:(执行即结束)
1.发请求
2.执行完成
3.另一个程序异步处理,处理完成后进行回调返回结果
同步的方式:
1.发请求
2.等待执行完成
3.有结果返回
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
public class ThreadPollDemo53 {
public static void main(String[] args) {
/**
* JDK8以后才有的创建方式
* 创建方式6: 根据当前的硬件CPU和任务量生成对应个数的线程池,并且是异步执行的。
* 生成的最大的线程数 = 当前电脑CPU核数(创建太多的线程可能导致线程争抢和线程切换的问题,降低了执行效率)
*/
ExecutorService executorService = Executors.newWorkStealingPool(); //根据当前硬件配置创建线程池
for (int i = 0; i < 10; i++) {
executorService.execute(new Runnable() {
@Override
public void run() {
System.out.println("线程名:"+Thread.currentThread().getName());
}
});
}
//等待线程池执行完成,就可以打印上面的内容了
while (!executorService.isTerminated()){
}
}
}
线程名:ForkJoinPool-1-worker-1
线程名:ForkJoinPool-1-worker-3
线程名:ForkJoinPool-1-worker-4
线程名:ForkJoinPool-1-worker-4
线程名:ForkJoinPool-1-worker-2
线程名:ForkJoinPool-1-worker-2
线程名:ForkJoinPool-1-worker-2
线程名:ForkJoinPool-1-worker-3
线程名:ForkJoinPool-1-worker-4
线程名:ForkJoinPool-1-worker-1
阿里巴巴手册规定:线程池不允许使用Executors去创建,因为不可控
线程池的两个重要的组件:
1.线程
2.任务队列
前六种线程池可能会存在的问题:
1.线程的数量不可控(比如创建带缓存的线程池的数量的时候)-> OOM
2.工作任务队列不可控(前六种的工作队列的默认大小是Integer.MAX_VALUE) -> OOM(内存溢出)
(7). 线程池创建方式7:(原始的线程创建方式)
import java.util.concurrent.LinkedBlockingDeque;
import java.util.concurrent.ThreadFactory;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
public class ThreadPollDemo55 {
private static int count = 1; //计数器
public static void main(String[] args) {
/**
* 参数1:核心线程的数量,线程池正常情况下的数量(正式员工的数量)
* 参数2:最大的线程数量,当有大量的任务的时候,可以创建的最多的线程数(这个值不能小于 参数1 的值,必须大于等于)(任务太多时临时工+正式员工的数量)
* 参数3:最大线程存活时间(临时工的存活时间)
* 参数4:配合参数3一起使用,时间单位
* 参数5:任务队列(一定要设置初始化大小,不然就是莫热门大小,可能会导致内存溢出) //5个参数就可以创建一个线程池了
* 参数6:线程工厂(设置线程池命名规则,优先级等)
* 参数7:拒绝策略(默认的拒绝策略是不执行任务,然后将错误抛出)
*/
//创建线程工厂
ThreadFactory threadFactory = new ThreadFactory() {
@Override
public Thread newThread(Runnable r) {
Thread thread = new Thread(r); //传递任务!!!!!!!
thread.setName("myThreadPoll-"+count++);
return null;
}
};
//原始的创建线程池的方式
ThreadPoolExecutor threadPoolExecutor =
new ThreadPoolExecutor(5,5,60,
TimeUnit.SECONDS,new LinkedBlockingDeque<>(100),threadFactory); //任务队列建议设置初始值,不设置默认队列时还是Ineger.MAX_VALUE
threadPoolExecutor.execute(new Runnable() {
@Override
public void run() {
System.out.println("线程名:"+Thread.currentThread().getName());
}
});
}
}
等待队列 LinkedBlockingDeque<>() 可以按序执行任务
线程池的5种拒绝策略:
- 默认拒绝策略:不执行任务直接抛出一个异常 ThreadPoolExecutor.AbortPolicy()
- 把当前的任务交给主线程执行(使用主线程执行存放不下的线程任务) ThreadPoolExecutor.CallerRunsPolicy()
- 丢弃任务队列里面最老的任务 ThreadPoolExecutor.DiscardOldestPolicy()
- 丢弃最新加入的任务 ThreadPoolExecutor.DiscardPolicy()
- 自定义拒绝策略new RejectedExecutionHandler(){}
自定义拒绝策略(舍弃最新的任务)的演示:
import java.util.concurrent.*;
public class ThreadPollDemo58 {
private static int count = 1;
public static void main(String[] args) {
//创建线程工厂
ThreadFactory threadFactory = new ThreadFactory() {
@Override
public Thread newThread(Runnable r) {
Thread thread = new Thread(r); //传递任务
thread.setName("myThreadPoll-"+count++);
return null;
}
};
//原始的创建线程池的方式
ThreadPoolExecutor threadPoolExecutor =
new ThreadPoolExecutor(1, 1, 60,
TimeUnit.SECONDS, new LinkedBlockingDeque<>(2),
threadFactory, new RejectedExecutionHandler() {
@Override
public void rejectedExecution(Runnable r, ThreadPoolExecutor executor) {
//自定义拒绝策略,舍弃最新的任务,可以写到日志里面,也可以存储到数据库,也可以什么都不做
System.out.println("执行了拒绝策略");
}
});
for (int i = 0; i < 10; i++) {
threadPoolExecutor.execute(new Runnable() {
@Override
public void run() {
System.out.println("线程名:"+Thread.currentThread().getName());
}
});
}
}
}
面试题:ThreadPoolExecutor 的执行流程 (三个重要的参数:核心线程数,最大线程数,任务队列)!!!!!!!
- 当任务量小于核心线程数的时候,他会创建一个线程来执行此任务,当任务量大于核心线程数的时候,并且没有空闲线程的时候,且当前线程池的线程数小于最大线程数,此时会将任务存到任务队列里面!!!!!注意:因为将多出来的任务存储到任务队列成本最小,所以此时线程池会将新任务存在任务队列当中,而非创建新线程来执行任务;
- 当前任务量比较大的时候,此时没有空闲的线程,并且任务队列已经满了,此时会判断当前线程池的任务数量是否大于等于最大线数,如果当前线程池的数量小于最大线程数,创建线程来执行任务;当前线程池的线程数量等于最大线程数并且任务队列已经满了,这个时候会去执行拒绝策略。
流程图如下:
线程池终止的方法:
- 线程池名.shutdown(); (1.停止接收新任务 2.等待任务队列中的任务执行结束然后终止)
- 线程池名.shutdownNow(); (1.停止接收新任务 2.立即终止线程池,任务队列的任务不会执行完)
线程池的状态:
- RUNNING:运行中,默认的状态
- SHUTDOWN:当前不能添加新任务了,但是会将当前任务队列里面的线程执行完(线程池调用shutdown会进入的状态)
- STOP:不执行新任务和队列里面的任务了(线程池调用shutdownNow会进入的状态)
- TIDYING:销毁的前置状态,当前线程池已经执行完了所有的任务(线程池里面线程数为0时)
TERMINATED:销毁状态
注意: 线程状态 != 线程池状态
执行线程池的两种方式:
- 执行任务没有返回值 excute(Runnable()…)
- 执行任务有返回值 submit(Runnable 无返回值/Callable() 有返回值)
import java.util.Random;
import java.util.concurrent.*;
public class ThreadPollDemo60 {
public static void main(String[] args) throws ExecutionException, InterruptedException {
ThreadPoolExecutor threadPoolExecutor = new ThreadPoolExecutor(1,2,10,
TimeUnit.SECONDS,new LinkedBlockingDeque<>());
threadPoolExecutor.execute(new Runnable() {
@Override
public void run() {
System.out.println("执行excute方法");
}
});
//用Future来接收返回值
Future<Integer> future = threadPoolExecutor.submit(new Callable<Integer>() { //返回值类型
@Override
public Integer call() throws Exception {
//生成随机数
int num = new Random().nextInt(10);
System.out.println("执行submit方法,生成随机数:"+num);
return num;
}
});
System.out.println("得到线程池的执行结果:"+future.get());
}
}
好了,线程池的创建方式到这里全部写完了。好好学习,加油!