目前卷文化盛行,为了增强面试能力,开始了无边无际的学习,无边界不是重点,重点是要深入1万米。言归正传,本文打算做一个多线程学习的系列文章,沉淀自我。
前言
本文主要是讲解创建线程的方式,包括基本的创建,和使用不同的线程池进行创建,基本用法,使用场景,底层代码原理剖析等。
一、概念
线程池提供了一种限制和管理资源(包括执行一个任务)。每个线程池还维护一些基本统计信息,例如已完成任务的数量。
- 使用线程池的好处:
降低资源消耗:通过重复利用已创建的线程降低线程创建和销毁造成的消耗。
提高响应速度:当任务到达时,任务可以不需要等到线程创建就能立即执行。
提高线程的可管理性:线程是稀缺资源,如果无限制的创建,不仅会消耗系统资源,还会降低系统的稳定性,使用线程池可以进行统一的分配,调优和监控。
- 不使用线程池的场景:
在流量波动大,如果不能动态的调整核心线程数,最大线程数,队列数,在资源允许的情况下,可以直接创建线程,不采用线程池也可以的。
二、创建多线程方式
2.1 继承Thread
继承Thread类,重写run方法。
package com.valley.juc.executor;
/**
* @author valley
* @date 2022/7/1
* @Description TODO
*/
public class ThreadSample extends Thread{
@Override
public void run() {
System.out.println("hello world!");
}
public static void main(String[] args) throws InterruptedException {
System.out.println("---start---");
ThreadSample ts =new ThreadSample();
ts.start();
ts.join();
System.out.println("---end---");
}
}
2.2 实现Runnable接口
注意,2.1继承Thread类,本质上就是实现Runnable,因为Thread实现了Runnable。
package com.valley.juc.executor;
import java.util.concurrent.Callable;
import java.util.concurrent.Executors;
/**
* @author valley
* @date 2022/7/1
* @Description TODO
*/
public class RunnableSample implements Runnable{
@Override
public void run() {
System.out.println("hello world!");
}
public static void main(String[] args) throws Exception {
// Thread thread=new Thread(new RunnableSample());
// thread.start();
// Callable<Object> call=Executors.callable(new RunnableSample());
// System.out.println(call.call());
Callable<Object> call=Executors.callable(new RunnableSample(),"ok");
System.out.println(call.call());
}
}
2.3 实现Callable接口
Callable和Runnable的区别
- 具体任务的方法,一个是run方法,一个call方法,call方法有返回值,注定了Callable可以获取执行人后的结果;
- 调用执行的方法,一个是start方法,一个是FutureTask类的run方法。注意start方法是不阻塞的,而run方法是阻塞的。当然,可以将FutureTask传给Thread,毕竟FutureTask实现了RunnableFuture接口,RunnableFuture接口继承了Runnable接口。new Thread(task2).start(),这样做的话,那两者的执行方法都是start方法,但是task2.get()会阻塞等待。
- Executors工具类可以实现两者功能转化。
package com.valley.juc.executor;
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.Executors;
import java.util.concurrent.FutureTask;
/**
* @author valley
* @date 2022/7/4
* @Description TODO
*/
public class CallableSample implements Callable {
private String i;
public CallableSample(String s) {
this.i=s;
}
@Override
public Object call() throws Exception {
System.out.println(i+" : hello world");
Thread.sleep(6000);
return "123";
}
public static void main(String[] args) throws Exception {
FutureTask task=new FutureTask(new CallableSample("1"));
FutureTask task2=new FutureTask(new CallableSample("2"));
task.run();
System.out.println("end 1");
String obj=(String)task.get();
System.out.println(obj);
new Thread(task2).start();
System.out.println("end 2");
String obj2=(String)task2.get();
System.out.println(obj2);
// Callable<Object> call= Executors.privilegedCallable(new CallableSample("1"));
// System.out.println(call.call());
}
}
}
三、利用工具类创建线程池
3.1 Executors工具类
3.1.1 FixedThreadPool
创建一个固定大小的线程池
package com.valley.juc.executor.threadpool;
import lombok.SneakyThrows;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
/**
* @author valley
* @date 2022/7/4
* @Description 创建一个固定大小的线程池
*/
public class NewFixedThreadPoolDemo {
public static void main(String[] args) {
//1.创建一个大小为5的线程池
ExecutorService threadPool= Executors.newFixedThreadPool(5);
//2.使用线程池执行任务一
for (int i=0;i<5;i++){
//给线程池添加任务
threadPool.submit(new Runnable() {
@SneakyThrows
@Override
public void run() {
Thread.sleep(1000);
System.out.println("线程名"+Thread.currentThread().getName()+"在执行任务1");
}
});
}
//2.使用线程池执行任务二
for (int i=0;i<8;i++){
//给线程池添加任务
threadPool.submit(new Runnable() {
@SneakyThrows
@Override
public void run() {
Thread.sleep(1000);
System.out.println("线程名"+Thread.currentThread().getName()+"在执行任务2");
}
});
}
}
}
3.1.2 SingleThreadExecutor
单个线程的线程池,充分利用线程池的优点
package com.valley.juc.executor.threadpool;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
/**
* @author valley
* @date 2022/7/4
* @Description
* 线程池的优点:
* 1.复用线程:不必频繁创建销毁线程
* 2.也提供了任务队列和拒绝策略
*/
public class NewSingleThreadExecutor {
public static void main(String[] args) {
ExecutorService service= Executors.newSingleThreadExecutor();
for (int i=0;i<5;i++){
int finalI = i;
service.submit(()->{
System.out.println(finalI +"线程名"+Thread.currentThread().getName());//CPU只创建了1个线程,名称始终一样
});
}
}
}
3.1.3 CachedThreadPool
带缓存的线程池,适用于短时间有大量任务的场景,但有可能会占用更多的资源;线程数量随任务量而定。
package com.valley.juc.executor.threadpool;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
/**
* @author valley
* @date 2022/7/4
* @Description 带缓存的线程池,适用于短时间有大量任务的场景,但有可能会占用更多的资源;线程数量随任务量而定。
*/
public class NewCachedThreadPoolDemo {
public static void main(String[] args) {
//创建线程池
ExecutorService service= Executors.newCachedThreadPool();
//有50个任务
for(int i=0;i<50;i++){
int finalI = i;
service.submit(()->{
System.out.println(finalI +"线程名"+Thread.currentThread().getName());//线程名有多少个,CPU就创建了多少个线程
});
}
}
}
3.1.4 ScheduledThreadPool
创建执行定时任务的线程池
package com.valley.juc.executor.threadpool;
import java.time.LocalDateTime;
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;
/**
* @author valley
* @date 2022/7/4
* @Description 创建执行定时任务的线程池
*/
public class NewScheduledThreadPoolDemo {
public static void main(String[] args) {
ScheduledExecutorService service = Executors.newScheduledThreadPool(5);//5个线程
System.out.println("添加任务:" + LocalDateTime.now());
once(service);
many(service);
}
private static void many(ScheduledExecutorService service) {
//间隔时间2s>执行时间0.1s;以间隔时间为准
//执行时间3s>间隔时间2s;以执行时间为准
// service.scheduleAtFixedRate(new Runnable() {
// @Override
// public void run() {
// System.out.println("执行任务:"+ LocalDateTime.now());
// }
// },3, 5, TimeUnit.SECONDS);//推迟3秒执行
//2.推迟3秒执行;上一次任务结束2s后,下一个任务开始执行
service.scheduleWithFixedDelay(new Runnable() {
@Override
public void run() {
System.out.println("执行任务:"+ LocalDateTime.now()+"线程名"+Thread.currentThread().getName());
try {
Thread.sleep(1000*2);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
},3,2,TimeUnit.SECONDS);//间隔时间成了4s;因为线程休眠了2s
}
/**
* 执行一次的定时任务
*/
public static void once(ScheduledExecutorService service) {
service.schedule(new Runnable() {
@Override
public void run() {
System.out.println("执行任务:"+ LocalDateTime.now());
}
},3, TimeUnit.SECONDS);//推迟3秒执行
}
}
3.1.5 NewSingleThreadScheduledExecutor
创建执行定时任务的单个线程的线程池
package com.valley.juc.executor.threadpool;
import java.time.LocalDateTime;
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;
/**
* @author valley
* @date 2022/7/4
* @Description newSingleThreadScheduledExecutor
* 创建执行定时任务的单个线程的线程池
*/
public class NewSingleThreadScheduledExecutorDemo {
public static void main(String[] args) {
ScheduledExecutorService service= Executors.newSingleThreadScheduledExecutor();
System.out.println("添加任务:"+ LocalDateTime.now());
service.schedule(new Runnable() {
@Override
public void run() {
System.out.println("执行任务:"+LocalDateTime.now());
}
},3, TimeUnit.SECONDS);//推迟3秒执行任务
service.scheduleAtFixedRate(new Runnable() {
@Override
public void run() {
System.out.println("执行任务2:"+LocalDateTime.now());
}
},3, 3,TimeUnit.SECONDS);
}
}
3.1.6 NewWorkStealingPool
根据自身电脑配置决定创建的线程数目
package com.valley.juc.executor.threadpool;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
/**
* @author valley
* @date 2022/7/4
* @Description 根据自身电脑配置决定创建的线程数目
*/
public class NewWorkStealingPool {
public static void main(String[] args) {
ExecutorService service= Executors.newWorkStealingPool();
for(int i=0;i<50;i++){
int finalI = i;
service.submit(()->{
System.out.println(finalI +"线程名"+Thread.currentThread().getName());//线程名有多少个,CPU就创建了多少个线程
});
}
//创建的为守护线程,JVM不会等待守护线程结束
while (!service.isTerminated()){
}
}
}
3.2 Spring包下的ThreadPoolTaskExecutor工具类
这个类则是spring包下的,是sring为我们提供的线程池类
package com.valley.juc.executor.threadpool;
import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;
import java.util.concurrent.ThreadPoolExecutor;
/**
* @author valley
* @date 2022/7/4
* @Description 这个类则是spring包下的,是sring为我们提供的线程池类
*/
public class ThreadPoolTaskExecutorDemo {
public static void main(String[] args) {
ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
int i = Runtime.getRuntime().availableProcessors();//获取到服务器的cpu内核
executor.setCorePoolSize(5);//核心池大小
executor.setMaxPoolSize(100);//最大线程数
executor.setQueueCapacity(1000);//队列程度
executor.setKeepAliveSeconds(1000);//线程空闲时间
executor.setThreadNamePrefix("task-asyn");//线程前缀名称
executor.setRejectedExecutionHandler(new ThreadPoolExecutor.AbortPolicy());//配置拒绝策略
for (int m=0;m<7;m++) {
int finalI = m;
executor.submit(() -> {
try {
Thread.sleep(finalI * 100);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + "任务名" + finalI);
});
}
}
}
3.3 CompletionService
在执行大量相互独立和同构的任务时,可以使用CompletionService
ExecutorCompletionService实现了CompletionService接口,从源码解析看,ExecutorCompletionService中内置了一个Future的BlockingQueue,在任务调用完成后,会将submit返回的future放入到BlockingQueue。用户可以通过completionQueue.take()得到future然后调用future.get()来获取任务执行结果。
package com.valley.juc.executor.threadpool;
import org.junit.Test;
import java.util.concurrent.*;
/**
* @author valley
* @date 2022/7/6
* @Description TODO
*/
public class CompletionServiceDemo {
@Test
public void method() throws InterruptedException, ExecutionException {
BlockingQueue q = new LinkedBlockingQueue();
ThreadPoolExecutor executor = new ThreadPoolExecutor(4,6,3, TimeUnit.SECONDS,q);
CompletionService<Integer> cs = new ExecutorCompletionService<>(executor);
cs.submit(new Callable<Integer>() {
@Override
public Integer call() throws Exception {
Thread.sleep(2000);
return 1;
}
});
cs.submit(new Callable<Integer>() {
@Override
public Integer call() throws Exception {
Thread.sleep(2000);
return 1;
}
});
for (int i = 0;i < 3;i++){
Integer f = cs.take().get();
System.out.println(f);
}
System.out.println("end");
}
}
3.4 CompletableFuture
-
CompletableFuture创建线程有2种方式:supplyAsync(有返回值)和:runAsync(无返回值)。
-
CompletableFuture,默认依靠fork/join框架启动新的线程实现异步与并发的。
-
它提供了函数式编程的能力,可以通过回调函数的方式处理返回结果,并且提供了转换和组合CompletableFuture的方法。
package com.valley.juc.executor.threadpool.completablefuture;
import org.junit.Test;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ExecutionException;
/**
* @author valley
* @date 2022/7/6
* @Description A任务B任务完成后,才执行C任务返回值的处理
*/
public class CompletableFutureDemo {
@Test
public void completableFuture1(){
System.out.println(Thread.currentThread().getName());
CompletableFuture<String> future1 = CompletableFuture.supplyAsync(() -> {
System.out.println("task1");
try {
Thread.sleep(4000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("future1 finished!"+Thread.currentThread().getName());
return "future1 finished!";
});
CompletableFuture<String> future2 = CompletableFuture.supplyAsync(() -> {
System.out.println("task2");
try {
Thread.sleep(4000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("future2 finished!"+Thread.currentThread().getName());
return "future2 finished!";
});
CompletableFuture<Void> future3 = CompletableFuture.allOf(future1, future2);
try {
future3.get();
System.out.println("task3"+Thread.currentThread().getName());
} catch (InterruptedException e) {
e.printStackTrace();
} catch (ExecutionException e) {
e.printStackTrace();
}
System.out.println("future1: " + future1.isDone() + " future2: " + future2.isDone());
}
}
四、手动创建线程池
4.1 ThreadPoolExecutor
- 在上面不管是ThreadPoolTaskExecutor还是Executors工具类,其实底层都是使用ThreadPoolExecutor来创建线程池。我们手动创建线程池,就是自己创建ThreadPoolExecutor。
《阿里巴巴Java开发手册》中强制线程池不允许使用 Executors 去创建,而是通过 ThreadPoolExecutor 的方式,这样的处理方式让写的同学更加明确线程池的运行规则,规避资源耗尽的风险。
package com.valley.juc.executor.threadpool;
import java.util.concurrent.*;
import java.util.stream.IntStream;
/**
* @author valley
* @date 2022/7/4
* @Description ThreadPoolExecutor
*
* 《阿里巴巴Java开发手册》中强制线程池不允许使用 Executors 去创建,而是通过 ThreadPoolExecutor 的方式,
* 这样的处理方式让写的同学更加明确线程池的运行规则,规避资源耗尽的风险。
*
* 手动创建线程池
*
* 7种参数分别是:
*
* 核心(最少)线程数
*
* 最大线程数
*
* 闲置可存活时间
*
* 描述(闲置可存活时间)的单位
*
* 任务队列
*
* 线程工厂
*
* 拒绝策略有5种:
*/
public class ThreadPoolExecutorDemo {
public static void main(String[] args) {
//线程工厂
ThreadFactory factory=new ThreadFactory() {
@Override
public Thread newThread(Runnable r) {
Thread thread=new Thread(r);
return thread;
}
};
//手动创建线程池
ThreadPoolExecutor threadPoolExecutor=new ThreadPoolExecutor(2, 4, 3, TimeUnit.SECONDS,
new LinkedBlockingDeque<>(2), factory,
//1.提示异常,拒绝执行多余的任务
// new ThreadPoolExecutor.AbortPolicy()
//2.忽略堵塞队列中最旧的任务
//new ThreadPoolExecutor.DiscardOldestPolicy()
//3.忽略最新的任务
//new ThreadPoolExecutor.DiscardPolicy()
//4.使用调用该线程池的线程来执行任务
//new ThreadPoolExecutor.CallerRunsPolicy()
//5.A自定义拒绝策略
new RejectedExecutionHandler() {
@Override
public void rejectedExecution(Runnable r, ThreadPoolExecutor executor) {
System.out.println("拒绝策略");
}
}
);
//任务
for (int i=0;i<7;i++){
int finalI=i;
threadPoolExecutor.submit(()->{
try {
Thread.sleep(finalI*100);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName()+"任务名"+finalI);
});
}
}
}
五、线程池的拒绝策略和处理流程
5.1 拒绝策略
- 拒绝策略有5种:
rejectedExectutionHandler参数字段用于配置绝策略,常用拒绝策略如下
AbortPolicy:用于被拒绝任务的处理程序,它将抛出RejectedExecutionException
CallerRunsPolicy:用于被拒绝任务的处理程序,它直接在execute方法的调用线程中运行被拒绝的任务。
DiscardOldestPolicy:用于被拒绝任务的处理程序,它放弃最旧的未处理请求,然后重试execute。
DiscardPolicy:用于被拒绝任务的处理程序,默认情况下它将丢弃被拒绝的任务。
5.1 线程池处理流程
- 线程池处理流程
查看核心线程池是否已满,不满就创建一条线程执行任务,否则执行第二步。
查看任务队列是否已满,不满就将任务存储在任务队列中,否则执行第三步。
查看线程池是否已满,即就是是否达到最大线程池数,不满就创建一条线程执行任务,否则就按照策略处理无法执行的任务。
六、常用场景
- 异步处理,如果不指定线程池,采用默认的线程池
- 异步调用接口,比如采用Okhttp
- 多线程读写