一.线程池
1.1.线程池的概念
用来装载线程的容器就叫做线程池
1.2 线程池的作用
让创建好的线程得以复用,避免由于频繁的创建和销毁线程对系统的资源消耗。
1.3 线程池的使用
创建任务类
public class MyRunnable implements Runnable{
@Override
public void run() {
for (int i = 1; i <= 5; i++) {
System.out.println(Thread.currentThread().getName() + "---" + i);
}
}
}
创建测试类,里面创建线程池对象
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
public class MyThreadPoolDemo {
public static void main(String[] args) {
/*
public static ExecutorService newCachedThreadPool()
创建一个没有上限的线程池
public static ExecutorService newFixedThreadPool (int nThreads)
创建有上限的线程池
*/
//1.获取线程池对象
ExecutorService pool1 = Executors.newFixedThreadPool(3);
//2.提交任务
pool1.submit(new MyRunnable());
pool1.submit(new MyRunnable());
pool1.submit(new MyRunnable());
pool1.submit(new MyRunnable());
//3.销毁线程池
//pool1.shutdown();
}
}
1.4 线程池内部的核心元素
分别代表
核心线程数量 int corePoolSize
线程池中最大线程的数量 int maximumPoolSize
空闲时间(多长时间) long keepAliveTime
空闲时间(单位,例如:秒) TimeUnit unit
阻塞队列(等待队列) BlockingQueue<Runnable> workQueue
创建线程的方式 ThreadFactory threadFactory
拒绝方案 RejectedExecutionHandler handler
1.5 线程池的四个情况
1.5.1 情况一
线程提交的任务数量等于核心线程数量
当提交三个任务时,线程池中就会创建3个线程去执行这3个任务
1.5.2 情况二
线程提交的任务数量大于核心线程数量
先创建3个核心线程,去处理前3个任务。剩余没有处理的任务前往等待区等候。等到3个核心线程把任务处理完了,归还到线程池中处于空闲状态时,再去执行等待区的未被执行的任务。
1.5.3 情况三
先创建3个核心线程,去处理前3个任务。剩余没有处理的任务前往等待区等候。再多的任务需要创建2个临时线程去执行这2个任务。(临时线程 = 最大线程 - 核心线程)
1.5.4 情况四
先创建3个核心线程,去处理前3个任务。剩余没有处理的任务前往等待区等候。再多的任务需要创建3个临时线程去执行这3个任务。因为临时线程也无法满足任务的量,剩余的任务就会被拒绝,不执行
二.手写一个简单的线程池
2.1 编写线程任务类-MyTask
用来创建线程执行的任务
public class MyTask implements Runnable{
/**
* 需求:
* 设计一个线程任务类-包含任务编号,每一个任务执行时间设计为0.2秒
*/
private int id;
public MyTask(int id){
this.id = id;
}
@Override
public void run() {
String name = Thread.currentThread().getName();
System.out.println(name+" 即将执行任务:"+id);
try {
Thread.sleep(200);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(" "+name+" 完成了任务:"+id);
}
@Override
public String toString() {
return "MyTask{" +
"id=" + id +
'}';
}
}
2.2 等待任务队列-MyWorker
public class MyWorker extends Thread{
/**
* 需求:
* 设计一个队列,用来保存所有的线程任务
*/
private String name;//线程的名字
private List<Runnable> tasks;
//构造方法
public MyWorker(String name,List<Runnable> tasks){
super(name);
this.tasks = tasks;
}
@Override
public void run() {
//判断集合中是否有任务,只要有,就一直执行任务
while(tasks.size() > 0){
Runnable r = tasks.remove(0);
r.run();
}
}
}
2.3 自定义的线程池-MyThreadPool
public class MyThreadPool {
/**
* 需求:
* 设计一个线程池类
* 1.成员变量:
* 1.1 任务队列 集合 需要控制线程安全问题
* 1.2 当前线程数量
* 1.3 核心线程数量
* 1.4 最大线程数量
* 1.5 任务队列的长度
* <p>
* 2.成员方法:
* 2.1 提交任务
* 将任务添加到集合中,需要判断是否超出了任务总长度
* 2.2 执行任务
* 判断当前线程的数量,决定创建核心线程还是临时线程
*/
//1. 设置一个集合 存储线程任务
private List<Runnable> tasks = Collections.synchronizedList(new LinkedList<>());
//2.当前线程的数量
private int num = 0;
//3.核心线程的数量
private int corePoolSize;
//4.最大线程的数量
private int maxSize;
//5.任务队列的最大长度
private int workSize;
public MyThreadPool(int corePoolSize, int maxSize, int workSize) {
this.corePoolSize = corePoolSize;
this.maxSize = maxSize;
this.workSize = workSize;
}
//2.1 提交任务
public void submit(Runnable r) {//MyThreadPool myThreadPool = new MyThreadPool(corePoolSize:2,maxSize:4,workSize:20);
if (tasks.size() > workSize) {
System.out.println("任务:" + r + "被丢弃了");
} else {
//添加任务
tasks.add(r);
//处理任务
execTask(r);
}
}
private void execTask(Runnable r) {//MyThreadPool myThreadPool = new MyThreadPool(corePoolSize:2,maxSize:4,workSize:20);
if (num < corePoolSize) {
new MyWorker("核心线程:" + num, tasks).start();
num++;
} else if (num < maxSize) {
new MyWorker("临时线程: " + num, tasks).start();
num++;
} else {
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("任务:" + r + " 被缓存了……");
}
}
}
2.4 创建测试类
public class MyTest {
public static void main(String[] args) {
//创建线程池对象
MyThreadPool mp = new MyThreadPool(2,4,10);
//创建多个任务
for(int i = 0 ; i < 20 ; i++){
//创建任务对象,并提交给线程池
MyTask myTask = new MyTask(i);
//提交给线程池
mp.submit(myTask);
}
}
}
三.Java内置的线程池
3.1线程池接口-ExecutorService
3.1.1 创建一个无上限的无参线程池
newCachedThreadPool()
public class Test {
public static void main(String[] args) {
ExecutorService es = Executors.newCachedThreadPool();
//执行十次任务
for (int i = 1; i <= 10 ; i++) {
final int r = i;
//使用submit方法将任务给到线程池
es.submit(new Runnable() {
@Override
public void run() {
//获取线程的名称
String name = Thread.currentThread().getName();
System.out.println(name+"执行了任务:"+r);
}
});
}
}
}
3.1.2 创建一个无上限的有参的线程池
newCachedThreadPool(修改线程)
任务类
public class MyRunnable implements Runnable {
private int id;
public MyRunnable(int id){
this.id = id;
}
//任务类,创建一个任务编号,在任务中,打印出是哪个线程正在执行任务
@Override
public void run() {
//获取线程的名称
String name = Thread.currentThread().getName();
System.out.println(name+"执行了任务:"+id);
}
}
public class Test2 {
public static void main(String[] args) {
ExecutorService es = Executors.newCachedThreadPool(new ThreadFactory() {
int n = 0;
@Override
public Thread newThread(Runnable r) {
//可以修改线程的名字
return new Thread(r,"自定义的线程"+n++);
}
});
//提交10个任务
for (int i = 1; i <= 10; i++) {
es.submit(new MyRunnable(i));
}
}
}
3.1.3 创建一个有上限的线程池
newFixedThreadPool(线程的数量)
public class Test2 {
public static void main(String[] args) {
//创建一个有3个线程的线程池
ExecutorService es = Executors.newFixedThreadPool(3);
es.submit(new Runnable() {
@Override
public void run() {
for(int i = 1 ; i <= 10 ; i++){
final int r = i;
String name = Thread.currentThread().getName();
System.out.println(name+"执行了任务:"+r);
}
}
});
}
}
newFixedThreadPool(线程的数量,修改线程)
public class Test {
public static void main(String[] args) {
ExecutorService es = Executors.newFixedThreadPool(3,new ThreadFactory() {
int n = 0;
@Override
public Thread newThread(Runnable r) {
return new Thread(r,"自定义的线程名称"+n++);
}
});
for(int i = 1 ; i <= 10 ; i++){
es.submit(new MyRunnable(i));
}
}
}
3.1.4 单个线程的线程池
用的还是之前的任务类
public class Test {
public static void main(String[] args) {
ExecutorService es = Executors.newSingleThreadExecutor();
for(int i = 1 ; i <= 10 ; i++){
es.submit(new MyRunnable(i));
}
}
}
3.1.5 对象的选择
性能模式
选newCachedThreadPool()去创建线程池
因为这是一个无上限的线程池,可以创建很多线程去交替执行任务。但是对系统压力较大。
所以如果硬件跟得上,任务执行时又在意性能。那么优先考虑使用newCachedThreadPool()去创建线程池
额定线程数量模式
选newFixedThreadPool(3)去创建线程池
因为这是一个可指定线程数量的的线程池,且指定几个就能创建几个。所以在硬件一般,任务执行时只需要额定数量的线程。那么可优先考虑使用newFixedThreadPool(3)去创建线程池
追求安全
选newSingleThreadExecutor()去创建线程池
因为这是一个只能创建单线程数量的线程池。即线程池中只有一个线程在执行任务。执行任务时不存在线程安全问题。所以在追求安全的前提下且不追求性能,那么可优先考虑使用单线程newSingleThreadExecutor()创建线程池去执行任务。
3.1.6 ExecutorService 对象方法的使用
3.1.6.1 shutdown()方法
public class Test {
public static void main(String[] args) {
//创建线程对象
ExecutorService es = Executors.newSingleThreadExecutor();
//提交任务
for (int i = 1; i <= 10; i++) {
es.submit(new MyRunnable(i));
}
//关闭线程池
es.shutdown();
//已经关闭线程池了,再次无法继续提交任务,会报错RejectedExecutionException
es.submit(new MyRunnable(666));
}
}
3.1.6.2 submit()方法
提交任务的方法
3.2 线程池-ScheduledExecutorService对象
对象的创建:
常用方法 :
任务类:
public class MyRunnable implements Runnable {
private int id;
public MyRunnable(int id) {
this.id = id;
}
@Override
public void run() {
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
//获取线程的名称
String name = Thread.currentThread().getName();
System.out.println(name + "执行了任务:" + id);
}
}
public class MyCallable implements Callable<Integer> {
@Override
public Integer call() throws Exception {
return 10;
}
}
1.
public class Test02 {
public static void main(String[] args) throws Exception {
//创建一个含有三个线程的延时线程池
ScheduledExecutorService ses = Executors.newScheduledThreadPool(3);
ses.schedule(new MyCallable(),2, TimeUnit.SECONDS);
}
}
2 .
public class Test01 {
public static void main(String[] args) {
//创建一个延迟线程池对象
ScheduledExecutorService ses = Executors.newScheduledThreadPool(3);
//创建任务
//任务线程名为1,延误2个单位,单位为秒
ses.schedule(new MyRunnable(1),2, TimeUnit.SECONDS);
//三个线程完成十个任务
for (int i = 1 ; i <= 10 ; i++) {
ses.schedule(new MyRunnable(i), 2, TimeUnit.SECONDS);
}
}
}
3 .
public class Test03 {
public static void main(String[] args) {
//1.获取一个具备延迟执行任务的线程池对象
ScheduledExecutorService es = Executors.newSingleThreadScheduledExecutor();
//任务的时间在这个period的时间里面,如果任务时间比period大,则为任务时间,比period小,则为period时间
es.scheduleAtFixedRate(new MyRunnable(1),1,5, TimeUnit.SECONDS);
System.out.println("over");
}
}
4.
public class Test04 {
public static void main(String[] args) {
//1.获取一个具备延迟执行任务的线程池对象
ScheduledExecutorService es = Executors.newSingleThreadScheduledExecutor();
//delay加上任务时间为间隔时间
es.scheduleWithFixedDelay(new MyRunnable(1),1,5, TimeUnit.SECONDS);
System.out.println("over");
}
}
3.3 线程池中-Future的理解
3.3.1 线程池中-Future的常用方法
任务类:
public class MyCall implements Callable<Integer> {
int i;
int j;
public MyCall(int i,int j){
this.i = i;
this.j = j;
}
@Override
public Integer call() throws Exception {
return i + j;
}
}
public class FutureDemo {
public static void main(String[] args) throws Exception {
//创建线程池对象
ExecutorService es = Executors.newCachedThreadPool();
//Future相当于接收任务,对任务进行各种判断,相当于控制任务的类
Future<Integer> f = es.submit(new MyCall(1, 1));
/*boolean b = f.cancel(true);//取消任务:取消返回true,未取消false
System.out.println("取消任务的结果是:"+b);*/
//取消任务后无法在继续执行,会报错CancellationException
test1(f);
}
private static void test1(Future<Integer> f) throws Exception{
//判断任务是否已经完成
boolean done = f.isDone();//判断任务是否完成。完成-则返回true,未完成-则返回false
System.out.println("第一次判断任务是否完成:"+done);
boolean cancelled = f.isCancelled();//判断任务是否取消
System.out.println("第一次判断任务是否取消:"+cancelled);
//如果有必要,等待任务计算完成,其结然后获取任务执行结果
Integer v = f.get();
System.out.println("任务执行的结果为:"+v);
boolean done2 = f.isDone();
System.out.println("第二次判断任务是否完成:"+done2);
boolean cancelled2 = f.isCancelled();//判断任务是否取消
System.out.println("第二次判断任务是否取消:"+cancelled2);
}
}
四.再次手写线程池
线程池类
public class ThreadPool {
// 初始化线程个数
//即默认线程池中线程的个数
private static final int default_pool_size = 2 ;
//定义一个长度用来设置线程的个数
private int poolSize = default_pool_size ;
//定义一个集合保存任务
private BlockingQueue<Runnable> blockingQueue = new LinkedBlockingQueue<Runnable>() ;
//构造方法
public ThreadPool() {
//初始化线程
initThread();
}
public ThreadPool(int poolSize) {
//线程池的数量要大于0
if (poolSize > 0){
this.poolSize = poolSize;
//初始化线程
initThread();
}
}
//开启线程的方法即初始化线程
public void initThread(){
//循环开启每一个线程
for (int i = 0; i < poolSize; i++) {
new TaskThread("线程-->" + i).start();
}
}
//提交线程
public void submit(Runnable runnable){
try {
blockingQueue.put(runnable);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
//定义一个内部类当作线程类用来创建线程
public class TaskThread extends Thread{
//构造方法,给线程赋值名称
public TaskThread(String name){
super(name);
}
@Override
public void run() {
while(true){
try {
//从任务集合中获取任务并执行
Runnable task = blockingQueue.take();
task.run();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
}
测试类
public class Test {
public static void main(String[] args) {
//创建线程池
ThreadPool threadPool = new ThreadPool(5);
//提交任务
for (int i = 0; i < 10; i++) {
int y = i;
threadPool.submit(new Runnable() {
@Override
public void run() {
System.out.println(Thread.currentThread().getName() + "---->>>处理了第"+(y + 1)+"个任务");
}
});
}
}
}
五.ThreadPoolExecutor创建线程
Executors中的静态方法去创建线程池其实底层都是通过ThreadPoolExecutor构建的
核心线程数量 int corePoolSize
线程池中最大线程的数量 int maximumPoolSize
空闲时间(多长时间) long keepAliveTime
空闲时间(单位,例如:秒) TimeUnit unit
阻塞队列(等待队列) BlockingQueue<Runnable> workQueue
创建线程的方式 ThreadFactory threadFactory
拒绝方案 RejectedExecutionHandler handler
5.1线程池的工作原理
线程池最多可执行的任务数 = 队列容量 + 最大线程数
1.客户端每次提交一个任务,线程池就会在核心线程池中创建一个工作线程来执行这个任务。当核心线程池中的线程已满时,则进入下一步操作。
2.把任务试图存储到工作队列中。如果工作队列没有满,则将新提交的任务存储在这个工作队列里,等待核心线程池中的空闲线程执行。如果工作队列满了,则进入下个流程。
3.线程池会再次在非核心线程池区域去创建新工作线程来执行任务,直到当前线程池总线程数量超过最大线程数时,就是按照指定的任务处理策略处理多余的任务。
5.2 任务拒绝策略
RejectedExecutionHandler是jdk提供的一个任务拒绝策略接口,它下面存在4个子类。
ThreadPoolExecutor.AbortPolicy: 丢弃任务并抛出RejectedExecutionException异常。是默认的策略。
ThreadPoolExecutor.DiscardPolicy: 丢弃任务,但是不抛出异常 这是不推荐的做法。
ThreadPoolExecutor.DiscardOldestPolicy:抛弃队列中等待最久的任务 然后把当前任务加入队列中。
ThreadPoolExecutor.CallerRunsPolicy: 调用任务的run()方法绕过线程池直接执行。
案例演示1-演示ThreadPoolExecutor.AbortPolicy任务处理策略
//演示ThreadPoolExecutor.AbortPolicy任务处理策略
public class Test {
public static void main(String[] args) {
ThreadPoolExecutor threadPoolExecutor = new ThreadPoolExecutor(1 ,
3 ,
20 ,
TimeUnit.SECONDS ,
new ArrayBlockingQueue<>(1) ,
Executors.defaultThreadFactory() ,
new ThreadPoolExecutor.AbortPolicy()) ;
//最大为3,执行5个任务
for(int x = 0 ; x < 5 ; x++) {
threadPoolExecutor.submit(() -> {
System.out.println(Thread.currentThread().getName() + "---->> 执行了任务");
});
}
}
}
结果:
丢弃任务并抛出异常
案例演示2-演示ThreadPoolExecutor.DiscardPolicy任务处理策略
public class Test2 {
public static void main(String[] args) {
/**
* 核心线程数量为1 , 最大线程池数量为3, 任务容器的容量为1 ,空闲线程的最大存在时间为20s
*/
ThreadPoolExecutor threadPoolExecutor = new ThreadPoolExecutor(1 , 3 , 20 , TimeUnit.SECONDS ,
new ArrayBlockingQueue<>(1) , Executors.defaultThreadFactory() , new ThreadPoolExecutor.DiscardPolicy()) ;
// 提交5个任务,而该线程池最多可以处理4个任务,当我们使用DiscardPolicy这个任务处理策略的时候,控制台不会报错
for(int x = 0 ; x < 5 ; x++) {
threadPoolExecutor.submit(() -> {
System.out.println(Thread.currentThread().getName() + "---->> 执行了任务");
});
}
}
}
丢弃任务但不报错
案例演示3-演示ThreadPoolExecutor.DiscardOldestPolicy任务处理策略
public class Test3 {
public static void main(String[] args) {
/**
* 核心线程数量为1 , 最大线程池数量为3, 任务容器的容量为1 ,空闲线程的最大存在时间为20s
*/
ThreadPoolExecutor threadPoolExecutor;
threadPoolExecutor = new ThreadPoolExecutor(1, 3, 20, TimeUnit.SECONDS,
new ArrayBlockingQueue<>(1), Executors.defaultThreadFactory(), new ThreadPoolExecutor.DiscardOldestPolicy());
// 提交5个任务
for (int x = 0; x < 5; x++) {
// 定义一个变量,来指定指定当前执行的任务;这个变量需要被final修饰
final int y = x;
threadPoolExecutor.submit(() -> {
System.out.println(Thread.currentThread().getName() + "---->> 执行了任务" + y);
});
}
}
}
哪个任务等待的时间最长,哪个任务被丢弃
案例演示4-演示ThreadPoolExecutor.CallerRunsPolicy任务处理策略
public class Test4 {
public static void main(String[] args) {
/**
* 核心线程数量为1 , 最大线程池数量为3, 任务容器的容量为1 ,空闲线程的最大存在时间为20s
*/
ThreadPoolExecutor threadPoolExecutor;
threadPoolExecutor = new ThreadPoolExecutor(1 , 3 , 20 , TimeUnit.SECONDS ,
new ArrayBlockingQueue<>(1) , Executors.defaultThreadFactory() , new ThreadPoolExecutor.CallerRunsPolicy());
// 提交5个任务
for(int x = 0 ; x < 5 ; x++) {
threadPoolExecutor.submit(() -> {
System.out.println(Thread.currentThread().getName() + "---->> 执行了任务");
});
}
}
}
调用任务的run()方法绕过线程池直接执行。
六.定时器的用法
Timer定时器,可以在指定的时间执行指定的任务。
public class Test {
public static void main(String[] args) {
//创建定时器对象
/*Timer timer = new Timer();*/
//为true则为守护线程
Timer timer = new Timer(true);
//2) 最常用 的方法schedule()
// timer.schedule(task,time); 在指定的time时间执行task任务
// timer.schedule(task, delay); 在延迟delay毫秒以后执行Task任务
// timer.schedule(task, delay, period); 在firstTime时间第一次执行task任务,
// 以后每隔period毫秒后重复执行一次task任务
//在3秒后打印一次时间, 以后每隔1秒重复打印一次
timer.schedule(new TimerTask() {
@Override
public void run() {
System.out.println(new Date());
}
}, 3000, 1000);
//main线程
for (int i = 0; i < 10; i++) {
System.out.println(i + "-------" + System.currentTimeMillis());
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}