文章目录
- 前言
- 线程的四种创建方式
- 四种方式下创建线程启动的区别
- 线程池的相关知识点
前言
近期对项目进行了回顾总结,来分享下我在项目中如何使用多线程来实现异步任务的,在分享之前,我们先来回顾下多线程的相关知识点
线程的四种创建方式
- 继承Thread类,重写run方法
- 实现Runnable接口,重写run方法
- 实现Callable接口,配合FutureTask来实现返回值,重写call方法
- 创建一个线程池,通过线程池调度
我们来看下代码:
import java.util.concurrent.*;
public class ThreadTest {
public static ExecutorService executor = Executors.newFixedThreadPool(10);
public static void main(String[] args) throws ExecutionException, InterruptedException {
// System.out.println("main......start.....");
// 1、通过继承Thread类
// Thread thread = new Thread01();
// thread.start();
// System.out.println("main......end.....");
// 2、通过实现Runnable接口
// Runable01 runable01 = new Runable01();
// new Thread(runable01).start();
// 3、通过FutureTask传参Callable,实现有返回值
// FutureTask<Integer> futureTask = new FutureTask<>(new Callable01());
// new Thread(futureTask).start();
// System.out.println(futureTask.get());
// service.execute(new Runable01());
System.out.println("main......start.....");
}
private static void threadPool() {
ExecutorService threadPool = new ThreadPoolExecutor(
200,
10,
10L,
TimeUnit.SECONDS,
new LinkedBlockingDeque<Runnable>(10000),
Executors.defaultThreadFactory(),
new ThreadPoolExecutor.AbortPolicy()
);
}
public static class Thread01 extends Thread {
@Override
public void run() {
System.out.println("当前线程:" + Thread.currentThread().getId());
int i = 10 / 2;
System.out.println("运行结果:" + i);
}
}
public static class Runable01 implements Runnable {
@Override
public void run() {
System.out.println("当前线程:" + Thread.currentThread().getId());
int i = 10 / 2;
System.out.println("运行结果:" + i);
}
}
public static class Callable01 implements Callable<Integer> {
@Override
public Integer call() throws Exception {
System.out.println("当前线程:" + Thread.currentThread().getId());
int i = 10 / 2;
System.out.println("运行结果:" + i);
return i;
}
}
}
四种方式下创建线程启动的区别
第一种和第二种都是无返回值的创建线程执行任务,第三种是有返回值的创建线程执行任务。
以上这三种创建线程的方式在业务中是不能出现的,因为它们的创建线程和销毁线程是非常消耗时间的,影响业务的响应时间,抛开这些不讲,它也非常的消耗资源,假设我现在有10万的并发请求打过来,如果我用以上三种的其中一种创建线程执行任务的话,理想状态下是需要创建10万个线程,这是不可能的,在期间肯定会造成内存爆了,服务器宕机的。再这样的情况下我们可以用线程池来解决这个问题
我们来归拢一下在开发中为什么使用线程池的原因
- 降低资源的消耗:重复利用已经创建好的线程来降低线程的创建和销毁带来的损耗
- 提高响应的速度:线程池中有的线程处于空闲状态,当任务来的时候,就无需创建线程就可以立即去执行
- 线程池会根据当前系统的特点对池内的线程进行优化处理,减少创建和销毁线程带来的系统开销,无限的创建线程不仅销毁系统资源,还会降低系统的稳定性,都交给线程池来统一管理了。
线程池的相关知识点
怎么样创建一个线程池呢?
可通过这段代码来执行
ExecutorService threadPool = new ThreadPoolExecutor(
200,
10,
10L,
TimeUnit.SECONDS,
new LinkedBlockingDeque<Runnable>(10000),
Executors.defaultThreadFactory(),
new ThreadPoolExecutor.AbortPolicy()
);
我们来看下构造器中的参数(深入源码)
/**
* Creates a new {@code ThreadPoolExecutor} with the given initial
* parameters.
*
* @param corePoolSize the number of threads to keep in the pool, even
* if they are idle, unless {@code allowCoreThreadTimeOut} is set
* @param maximumPoolSize the maximum number of threads to allow in the
* pool
* @param keepAliveTime when the number of threads is greater than
* the core, this is the maximum time that excess idle threads
* will wait for new tasks before terminating.
* @param unit the time unit for the {@code keepAliveTime} argument
* @param workQueue the queue to use for holding tasks before they are
* executed. This queue will hold only the {@code Runnable}
* tasks submitted by the {@code execute} method.
* @param threadFactory the factory to use when the executor
* creates a new thread
* @param handler the handler to use when execution is blocked
* because the thread bounds and queue capacities are reached
* @throws IllegalArgumentException if one of the following holds:<br>
* {@code corePoolSize < 0}<br>
* {@code keepAliveTime < 0}<br>
* {@code maximumPoolSize <= 0}<br>
* {@code maximumPoolSize < corePoolSize}
* @throws NullPointerException if {@code workQueue}
* or {@code threadFactory} or {@code handler} is null
*/
public ThreadPoolExecutor(int corePoolSize,
int maximumPoolSize,
long keepAliveTime,
TimeUnit unit,
BlockingQueue<Runnable> workQueue,
ThreadFactory threadFactory,
RejectedExecutionHandler handler) {
corePoolSize:核心线程数量
maximumPoolSize:最大线程数量
keepAliveTime:线程池中超过核心线程数量在没有任务执行时最大生存时间,超过这个时间该线程就会被销毁
unit:时间单位
workQueue:工作队列,当任务数量超过核心线程的数量是,就会将这些任务放到工作对列中去,只要有空闲线程,这些空闲线程就会去队列中去取
threadFactory:创建线程的工厂
handler:当线程池不能再接受新任务时,采取的拒绝策略
线程池的工作流程
当创建一个线程池后,当提交的任务小于核心线程的数量时,核心线程就会立即去执行任务,当数量超过了核心线程数量的时候,就会将这些任务放到工作队列中去,当工作队列满了的话,就会创建非核心线程去执行任务,当线程池中的线程数量达到最大线程数量的时候,如果此时再提交任务,就会触发拒绝策略拒绝任务,当线程池中的任务都执行完成之后,就会有线程处于空闲状态,当处于空闲时间达到keepalive时间后,那些非核心线程就会被销毁,核心线程默认不会被销毁
jdk提供好的线程池
// 固定线程数量的线程池
public static ExecutorService newFixedThreadPool(int nThreads) {
return new ThreadPoolExecutor(nThreads, nThreads,
0L, TimeUnit.MILLISECONDS,
new LinkedBlockingQueue<Runnable>());
}
// 核心线程为0,但可以任意创建线程,最后都能被销毁
public static ExecutorService newCachedThreadPool() {
return new ThreadPoolExecutor(0, Integer.MAX_VALUE,
60L, TimeUnit.SECONDS,
new SynchronousQueue<Runnable>());
}
// 单个线程的线程池
public static ExecutorService newSingleThreadExecutor() {
return new FinalizableDelegatedExecutorService
(new ThreadPoolExecutor(1, 1,
0L, TimeUnit.MILLISECONDS,
new LinkedBlockingQueue<Runnable>()));
}
// 执行定时任务的线程池,它可以指定多长时间以后执行任务
public static ScheduledExecutorService newScheduledThreadPool(int corePoolSize) {
return new ScheduledThreadPoolExecutor(corePoolSize);
}
拒绝策略
第一种拒绝策略是:当线程池不能再接受任务的时候,可以去调用将要提交任务的run方法,只不过是同步的方式了,不是异步执行了
第二种拒绝策略是:当线程池不能再接受任务的时候,当又要有任务提交时,直接拒绝执行任务,并抛出异常
第三种拒绝策略是:当线程池不能再接受任务的时候,当有任务提交时,将这个任务删除,但不抛出异常
第四种拒绝策略是:当线程池不能再接受任务的时候,当有任务提交时,将队列中生存时间最长的任务删除,将当前任务加入到工作队列中去