文章目录
线程池
1. 线程池的主要处理流程
- 提交一个新任务到线程池时,首先判断核心线程数是否都在执行任务:
- 如果不是,那么需要创建一个线程来执行新提交的任务;
- 如果所有的核心线程都在执行任务,那么 进入下一步。
- 判断线程池中的工作队列是否已满。如果工作队列还没有满,那么新提交的任务就进入工作线程进行等待;如果工作队列已经满了,那进入下一步;
- 判断线程池中所有的线程是否都处于工作状态,如果没有,那么就创建一个线程来处理任务;如果线程所有的线程都已经处于工作状态,那么就按照拒绝策略来处理该任务。
2. 创建线程的方法
-
继承Thread类
- 创建一个继承于Thread类的子类
- 重写Thread类的run()
- 将此线程执行的操作声明在run()中
- 创建Thread类的子类的对象 / 实例
- 通过此对象调用start()方法
public class CreateThreadDemo1 extends Thread{ @Override public void run() { for (int i = 0; i < 10; i++) { System.out.print(i * i + " "); } } public static void main(String[] args) { CreateThreadDemo1 ct1 = new CreateThreadDemo1(); ct1.start(); } }
-
实现Runnable接口
- 创建一个实现了Runnable接口的类
- 实现类去实现Runnable中的抽象方法:run()
- 创建实现类的对象
- 将此对象作为参数传递到Thread类的构造器中,创建Thread类的对象
- 通过Thread类的对象调用start()
public class CreateThreadDemo2 implements Runnable{ @Override public void run() { System.out.println("创建线程方法二"); } public static void main(String[] args) { CreateThreadDemo2 ct2 = new CreateThreadDemo2(); Thread thread = new Thread(ct2); thread.start(); } }
-
实现Callable接口
- 创建一个实现Callable的实现类
- 实现call方法,将此线程需要执行的操作声明在call()中
- 创建Callable接口实现类的对象
- 将此Callable接口实现类的对象作为参数传递到FutureTask构造器中,创建FutureTask的对象
- 将FutureTask的对象作为参数传递到Thread类的构造器中,创建Thread对象,并调用start()
- 获取Callable中call方法的返回值 (boolean)
public class CreateThreadDemo3 implements Callable { @Override public Object call() throws Exception { System.out.println("创建线程方法三"); return false; } public static void main(String[] args) throws ExecutionException, InterruptedException { CreateThreadDemo3 ct3 = new CreateThreadDemo3(); FutureTask<Boolean> task = new FutureTask<Boolean>(ct3); Thread thread = new Thread(task); thread.start(); System.out.println(task.get()); //false,获取call方法中的返回值 } }
-
使用线程池的方式
- 使用Executors方式创建,不推荐,具体原因如下图所示
- 通过ThreadPoolExecutor方式创建
Integer.MAX_VALUE = 2147483647
Integer.MIN_VALUE = -2147483648
3. 线程池主要解决两个问题
- **当执行大量异步任务时线程池能够提供较好的性能。**在不使用线程池时,每当需要执行异步任务时,都直接new一个线程来运行,而线程的创建和销毁都是需要开销的。线程池里面的线程可以复用,不需要每次执行异步任务时新建和销毁。
- 线程池提供了一种资源限制和管理的手段,比如限制线程的个数,动态增加线程等。
优点:
- 方便管理;
- 提高响应的速度
- 降低资源的消耗; 可以复用
4. 三大方法
Executors.newSingleThreadExecutor(); //单个线程
Executors.newFixedThreadPool(10);//创建一个固定大小的线程池
Executors.newCachedThreadPool(); //可伸缩的线程池
关闭线程池:使用shutdown()函数关闭。
上面三条语句对应的源码:本质是new一个ThreadPoolExecutor对象
public static ExecutorService newSingleThreadExecutor() {
return new FinalizableDelegatedExecutorService
(new ThreadPoolExecutor(1, 1,
0L, TimeUnit.MILLISECONDS,
new LinkedBlockingQueue<Runnable>()));
}
public static ExecutorService newFixedThreadPool(int nThreads) {
return new ThreadPoolExecutor(nThreads, nThreads,
0L, TimeUnit.MILLISECONDS,
new LinkedBlockingQueue<Runnable>());
}
public static ExecutorService newCachedThreadPool() {
return new ThreadPoolExecutor(0, Integer.MAX_VALUE,
60L, TimeUnit.SECONDS,
new SynchronousQueue<Runnable>());
}
5. 七大参数
ThreadPoolExecutor的底层源码
public ThreadPoolExecutor(int corePoolSize, //核心线程数
int maximumPoolSize, //最大线程数
long keepAliveTime, //线程存活时间
//超时单位
TimeUnit unit,
//阻塞队列
BlockingQueue<Runnable> workQueue,
// 线程工厂
ThreadFactory threadFactory,
// 拒绝策略
RejectedExecutionHandler handler) {
if (corePoolSize < 0 ||
maximumPoolSize <= 0 ||
maximumPoolSize < corePoolSize ||
keepAliveTime < 0)
throw new IllegalArgumentException();
if (workQueue == null || threadFactory == null || handler == null)
throw new NullPointerException();
this.acc = System.getSecurityManager() == null ?
null :
AccessController.getContext();
this.corePoolSize = corePoolSize;
this.maximumPoolSize = maximumPoolSize;
this.workQueue = workQueue;
this.keepAliveTime = unit.toNanos(keepAliveTime);
this.threadFactory = threadFactory;
this.handler = handler;
}
①corePoolSize:线程池的核心线程数,当提交一个任务到线程池时,线程池会创建一个线程来执行任务,即使其他空闲的基本线程能够执行新任务也会创建线程,等到需要执行的任务数大于线程池基本大小时就不再创建。说白了就是,即是线程池里没有任何任务,也会有corePoolSize个线程在候着等任务。
②maximumPoolSize:最大线程数,不管你提交多少任务,线程池里最多工作线程数就是maximumPoolSize。
③keepAliveTime:线程的存活时间。当线程池里的线程数大于corePoolSize时,如果等了keepAliveTime时长还没有任务可执行,则线程退出。
④unit超时单位:这个用来指定keepAliveTime的单位,比如秒:TimeUnit.SECONDS。
⑤workQueue:用于保存等待执行任务的阻塞队列,提交的任务将会被放到这个队列里。
- ArrayBlockingQueue:是一个基于数组结构的有界阻塞队列,此队列按FIFO(先进先出)原则对元素进行排序。
- LinkedBlockingQueue:一个基于链表结构的阻塞队列,此队列按FIFO排序元素,吞吐量通常要高于ArrayBlockingQueue。静态工厂方法Executors.newFixedThreadPool()使用了这个队列。
- SynchronousQueue:一个不存储元素的阻塞队列。每个插入操作必须等到另一个线程调用移除操作,否则插入操作一直处于阻塞状态,吞吐量通常要高于Linked-BlockingQueue,静态工
厂方法Executors.newCachedThreadPool使用了这个队列。 - PriorityBlockingQueue:一个具有优先级的无限阻塞队列。
⑥threadFactory:线程工厂,用来创建线程。主要是为了给线程起名字,默认工厂的线程名字:pool-1-thread-3。
⑦handler:拒绝策略,即当线程和队列都已经满了的时候,应该采取什么样的策略来处理新提交的任务。默认策略是AbortPolicy(抛出异常),其他的策略还有:CallerRunsPolicy(只用调用者所在线程来运行任务)、DiscardOldestPolicy(丢弃队列里最近的一个任务,并执行当前任务)、DiscardPolicy(不处理,丢弃掉)
自定义一个线程池,使ThreadPoolExecutor,最大线程应该如何定义???
分为两种:
-
1)CPU密集型:cpu有多少个核,就可以有多少个线程同时执行。这里可以将最大线程数设置和cpu的核数相同,保证执行效率最高。
// 获取CPU的核数(即任务管理器-->性能 --> CPU --> 逻辑处理器的大小) Runtime.getRuntime().availableProcessors()
-
2)IO密集型:判断程序中十分耗IO的线程,只要在这个基础上比耗IO线程数大就行。可以设置为 2倍。
6. 四种拒绝策略
-
AbortPolicy(抛出异常,默认):
-
CallerRunsPolicy(只用调用者所在线程来运行任务):哪儿来的回哪。Main线程执行。
-
DiscardOldestPolicy(丢弃队列里最近的一个任务,并执行当前任务):队列满了,不会抛出异常。尝试与第一个竞争,如果竞争失败,那么仍然会被抛弃。
-
DiscardPolicy:队列满了,不处理,直接丢弃掉
7. 向线程池提交任务
可以使用两个方法向线程池提交任务,分别为execute()和submit()方法。
- execute()方法用于提交不需要返回值的任务,所以无法判断任务是否被线程池执行成功。
- submit()方法用于提交需要返回值的任务。线程池会返回一个future类型的对象,通过这个future对象可以判断任务是否执行成功,并且可以通过future的get()方法来获取返回值,get()方法会阻塞当前线程直到任务完成,而使用get(long timeout,TimeUnit unit)方法则会阻塞当前线程一段时间后立即返回,这时候有可能任务没有执行完。
8. 线程池都有哪些状态?
有如下五个状态:
private static final int RUNNING = -1 << COUNT_BITS;
private static final int SHUTDOWN = 0 << COUNT_BITS;
private static final int STOP = 1 << COUNT_BITS;
private static final int TIDYING = 2 << COUNT_BITS;
private static final int TERMINATED = 3 << COUNT_BTS;
- Running: 一旦被创建,就处于此状态,可以接受新任务以及对已经添加的任务进行处理.
- Shutdown: 此时不接收新任务,但是可以处理已添加的任务。在此状态下,阻塞队列为空并且线程池中执行的任务也为空时,就会由 Shutdown-> Tidying。
- Stop:此状态不接收新任务,不处理已添加任务,并且会中断正在处理的任务。当线程池在STOP状态下,线程池中执行的任务为空时,就会由STOP -> Tidying状态。
- Tidying:当所有的任务已终止,ctl记录的任务数变为0,线程池会变成tidying状态,当线程池变为TIDYING状态会执行terminated()方法。
- Terminated:线程池彻底终止会变成这个状态。当在TIDYING状态,执行完terminated()方法后,就会由TIDYING状态变为TERMINATED状态。
9. 关闭线程池
两种方法可以关闭线程池:
-
shutdown()
public void shutdown() { final ReentrantLock mainLock = this.mainLock; mainLock.lock(); try { checkShutdownAccess(); advanceRunState(SHUTDOWN); interruptIdleWorkers(); onShutdown(); // hook for ScheduledThreadPoolExecutor } finally { mainLock.unlock(); } tryTerminate(); }
-
shutdownNow()
public List<Runnable> shutdownNow() { List<Runnable> tasks; final ReentrantLock mainLock = this.mainLock; mainLock.lock(); try { checkShutdownAccess(); advanceRunState(STOP); interruptWorkers(); tasks = drainQueue(); } finally { mainLock.unlock(); } tryTerminate(); return tasks; }
原理:
遍历线程池中的工作线程,然后逐个调用线程的interrupt方法来中断线程,所以无法响应中断的任务可能永远无法终止。
区别:
- shutdownNow首先将线程池的状态设置成STOP,然后尝试停止所有的正在执行或暂停任务的线程,并返回等待执行任务的列表;
- shutdown只是将线程池的状态设置成SHUTDOWN状态,然后中断所有没有正在执行任务的线程。
- 通常调用shutdown方法来关闭线程池,如果任务不一定要执行完,则可以调用shutdownNow方法。