一、初始化线程的四种方式
1.继承Thread
2.实现Runnable
3.实现Callable接口+FutureTask
4.线程池:两种主要初始化方式Executors.newFixedThreadPool()或new ThreadPoolExecutor()
方式1和方式2:主进程无法获取线程的运算结果。
方式3:主进程可以获取运算结果,但是不利于控制服务器中的线程资源,从而导致服务器资源耗尽。
方式4:线程池性能稳定,可以获取执行结果、捕获异常。但是在业务复杂情况下,一个异步调用可能会依赖于另一个异步调用的执行结果。
简单实用方式
继承Thread
public class ThreadTest {
public static void main(String[] args) {
System.out.printf("启动开始----");
/**
* 继承Thread
*/
Thread thread = new Thread01();
// 启动线程
thread.start();
System.out.printf("启动结束----")
}
static class Thread01 extends Thread {
@Override
public void run() {
System.out.printf("当前线程:"+Thread.currentThread().getId());
int i = 10/2;
System.out.printf("运行结果"+i);
}
}
}
运行结果:
实现Runnable
public class ThreadTest {
public static void main(String[] args) {
System.out.printf("启动开始----");
/**
* 实现Runnable
*/
Runnable01 runnable01 = new Runnable01();
new Thread(runnable01).start();
System.out.printf("启动结束----");
}
static class Runnable01 implements Runnable{
@Override
public void run() {
System.out.printf("当前线程:"+Thread.currentThread().getId());
int i = 10/2;
System.out.printf("运行结果"+i);
}
}
}
运行结果:
实现Callable接口+FutureTask
如果使用:futureTask.get()获取结果,将会变成阻塞等待。
public class ThreadTest {
public static void main(String[] args) {
System.out.printf("启动开始----");
/**
* 实现Callable接口+FutureTask
*/
// FutureTask底层继承Runnable
FutureTask futureTask = new FutureTask<>(new Callable01());
new Thread(futureTask).start();
// 可以会获取返回值,如果获取将会变成阻塞等待
try {
// 等待整个线程执行完成,获取返回结果(阻塞等待)
Object o = futureTask.get();
System.out.printf("o----"+o);
} catch (InterruptedException e) {
e.printStackTrace();
} catch (ExecutionException e) {
e.printStackTrace();
}
System.out.printf("启动结束----");
}
static class Callable01 implements Callable<Integer> {
@Override
public Integer call() throws Exception {
System.out.printf("当前线程:"+Thread.currentThread().getId());
int i = 10/2;
System.out.printf("运行结果"+i);
return i;
}
}
}
运行结果:
线程池
public class ThreadTest {
// 创建线程池
static ExecutorService service = Executors.newFixedThreadPool(10);
public static void main(String[] args) {
System.out.printf("启动开始----");
/**
* 线程池
*/
// submit提交线程有返回参数,execute执行没有返回参数
service.execute(new Runnable01());
}
static class Runnable01 implements Runnable{
@Override
public void run() {
System.out.printf("当前线程:"+Thread.currentThread().getId());
int i = 10/2;
System.out.printf("运行结果"+i);
}
}
}
二、线程池
为什么使用线程池,如果每个业务处理都使用原生的线程方法,会不断占用线程。在高并发环境下会将资源耗尽,无法做到总管理线程。所以我们将所有的多线程异步任务都交给线程池执行。
注意不应该执行每个业务都创建一个池,而是系统里最好控制线程池的数量。
线程池介绍
1.Executors.newFixedThreadPool,创建一个固定大小的线程池。
2.ThreadPoolExecutor,创建⼀个抢占式执⾏的线程池,是java中最常用的线程池。
3.Executors.newSingleThreadExecutor,
创建⼀个可缓存的线程池。
4.Executors.newScheduledThreadPool,
创建单个线程数的线程池,可保证执行顺序。
5.scheduleAtFixedRate,创建一个可执行延迟任务的线程池。
6.Executors.newWorkStealingPool,创建一个
⼀个单线程的可以执⾏延迟任务的线程池。
ThreadPoolExecutor为最原始的线程池,也是我们主要使用的线程池。
ThreadPoolExecutor线程池
创建ThreadPoolExecutor比较复杂,最完整的有七个参数:
1.corePoolSize:核心线程数,线程池创建好后就准备就绪的线程数量,不会因为空闲而回收,等待接受异步任务执行。(除非设置允许核心线程销毁参数)
2.maximumPoolSize:最大线程数量,线程池允许的最大线程数。控制资源并发。
3.keepAliveTime:存活时间,当前线程数量大于核心数量,空闲线程等待最长时间,然后释放。
4.TimeUnit:时间单位,存活时间的时间单位。
5.BlockingQueue<Runnable>:阻塞队列,如果任务过多,就会将目前多的任务放到队列中。只要有空闲的线程,将会从队列中取出新的任务执行。
6.ThreadFactory:线程创建工厂。
--DefaultThreadFactory:默认工厂,创建普通线程。
--PriorityThreadFactory:自定义工厂,创建具有优先级的线程,可指定线程优先级。
--CustomizableThreadFactory:自定义工厂,创建自定义的线程,可自定义线程名、优先级、守护线程标志、线程组等属性。
--NamedThreadFactory:自定义工厂,创建有名字的线程,会为每个线程设置一个名字。
--SpringThreadFactory:Spring框架提供的线程工厂,用于创建可管理的线程。会创建一个新的线程,并将其注册到Spring的线程管理器中,以便管理线程的状态和执行情况。
7.RejectedExecutionHandler,如果队列满了,按照指定的拒绝策略,拒绝执行任务。
--AbortPolicy:默认策略,抛弃策略,当无法执行更多任务时抛弃任务并抛出异常。
--CallerRunsPolicy:调用者运行策略,当线程池无法处理新任务时,将任务返回给提交任务的线程来同步执行。
--DiscardPolicy:忽视策略,当线程池无法处理新任务时,直接抛弃,无任何提示。
--DiscardOldestPolicy:抛弃旧策略,当线程池无法处理新任务时,抛弃队列中最早提交的任务,并尝试重新提交新任务。
运行流程
1.线程池创建,准备好core数量的核心线程,准备接受任务。
2.新的任务进来会用core准备好的空闲线程执行。
(1)core满了,就将再进来的任务放到阻塞队列中。空闲的core就会自己去阻塞队列获取任务执行。
(2)阻塞队列满了,直接开新线程执行,最大能开到maximumPoolSize指定的数量。
(3)maximumPoolSize满了就用RejectedExecutionHandler策略拒绝任务。
(4)maximumPoolSize都执行好了。最大线程数量空闲的线程会在keepAliveTime指定时间后销毁线程,最终保持到core大小。
为什么使用线程池
降低资源的消耗:
通过重复利用已经创建好的线程降低线程的创建和销毁带来的损耗。
提高响应速度:
因为线程池中的线程数没有超过线程池的最大上限时,有的线程处于等待分配任务的状态,当任务来时无需创建新的线程就能执行。
提高线程的可管理性:
线程池回根据当前系统特点对池内的线程进行优化处理,减少创建和销毁带来的系统开销。无限的创建和销毁线程消耗系统资源、降低系统的稳定性。使用线程池进行统一分配。
异步处理请移步:
Java线程池异步CompletableFuture,创建一步对象、回调方法、线程串行化、两任务组合、多任务组合-CSDN博客