上文链接:锁的智慧【java线程高并发提升二】
1、线程池是个什么东西?
线程池是一种多线程处理形式,处理过程中将任务添加到队列,然后在创建线程后自动启动这些任务。线程池线程都是后台线程。每个线程都使用默认的堆栈大小,以默认的优先级运行,并处于多线程单元中。如果某个线程在托管代码中空闲(如正在等待某个事件),则线程池将插入另一个辅助线程来使所有处理器保持繁忙。如果所有线程池线程都始终保持繁忙,但队列中包含挂起的工作,则线程池将在一段时间后创建另一个辅助线程但线程的数目永远不会超过最大值。超过最大值的线程可以排队,但他们要等到其他线程完成后才启动。
2、线程池的五种状态?
废话不说,直接上代码(代码在RejectedExecutionHandler -》rejectedExecution -》DiscardPolicy中)。
// runState is stored in the high-order bits
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_BITS;
状态 | 介绍 |
---|---|
RUNNING | 运行状态 |
SHUTDOWN | 关闭状态,此状态下线程池不在接收新任务,只处理已添加任务。 |
STOP | 停止状态,此状态下线程池停止所有任务。 |
TIDYING | 整体桩体,此状态下线程池中任务数为0 |
TERMINATED | 终结状态 |
各个状态转换如下:
3、线程池为什么这么创建、参数几何?
线程池创建过程如下:
package Three_ThreadFactory;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
public class createFactory {
public static void main(String[] args) {
//线程池创建方式
ExecutorService theadpool = Executors.newFixedThreadPool(1);
theadpool.execute(()->{
System.out.println("当前线程名字是:" + Thread.currentThread().getName());
});
}
}
接下来我们要开始讲述源码了!!! newFixedThreadPool : 固定线程池,其参数中可以设置线程数量,比如上面设置了1。定位到源码:
public static ExecutorService newFixedThreadPool(int nThreads) {
return new ThreadPoolExecutor(nThreads, nThreads,
0L, TimeUnit.MILLISECONDS,
new LinkedBlockingQueue<Runnable>());
}
该方法返回一个ThreadPoolExecutor对象。
ThreadPoolExecutor:线程池程序。定位到源码:
public ThreadPoolExecutor(int corePoolSize,
int maximumPoolSize,
long keepAliveTime,
TimeUnit unit,
BlockingQueue<Runnable> workQueue) {
this(corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue,
Executors.defaultThreadFactory(), defaultHandler);
}
ThreadPoolExecutor可以设置参数及作用如下(相应源码可以自行定位查看):
参数名 | 作用 |
---|---|
corePoolSize | 核心线程数量 |
maximumPoolSize | 最大可容载线程数量 |
keepAliveTime | 线程中空闲线程最长不释放时间(0表示一直不释放) |
workQueue | 存放等待执行的任务 |
threadFactory | 线程池创建工厂,用于创建具体线程,默认实现为:defaultThreadFactory() |
defaultHandler | 当workQuery无法新加任务时,提供拒绝策略,默认拒绝策略:AbortPolicy(抛出异常) |
defaultHandler:处理策略,在线程池程序调用execute后通过reject(command)可以设置相应拒绝策略。
4、线程池执行机制真是这样的!
//执行过程
public static void main(String[] args) {
//
ExecutorService theadpool = Executors.newFixedThreadPool(1);
theadpool.execute(()->{
System.out.println("当前线程名字是:" + Thread.currentThread().getName());
});
}
public void execute(Runnable command) {
if (command == null)
throw new NullPointerException();
int c = ctl.get();
if (workerCountOf(c) < corePoolSize) {
if (addWorker(command, true))
return;
c = ctl.get();
}
if (isRunning(c) && workQueue.offer(command)) {
int recheck = ctl.get();
if (! isRunning(recheck) && remove(command))
reject(command);
else if (workerCountOf(recheck) == 0)
addWorker(null, false);
}
else if (!addWorker(command, false))
reject(command);
}
对以上源码的描述:调用execute方法后,检查线程池的运行状态和工作线程数量,如果工作线程的线程数少于核心线程数,则会创建一个新的线程来执行给定的任务,调用addWorker来执行任务,如果线程池线程数量超过核心线程数量,把任务放到工作任务队列中,若工作队列未溢出,则添加任务,添加时重新检查线程池状态,如果没有继续运行(不是RUNNING状态)就把任务移除,使用拒绝策略来处理当前任务;否则将创建或唤醒工作线程来执行任务(线程池非RUNNING状态或添加任务失败后,使用拒绝策略来处理当前任务)。
5、线程池中的LinkedBlockingQueue阻塞队列。
(1) 接口及类继承展示。
(2)属性讲述名称 | 类型 | 含义 |
---|---|---|
capacity | int | 队列长度 |
count | AtomicInteger | 队列元素数量 |
head | Node | 头结点 |
last | Node | 尾节点 |
takeLock | ReentrantLock | 取出节点锁 |
notEmpty | Condition | 用以唤醒取出某节点的线程 |
putLock | ReentranLock | 添加节点的锁 |
notFull | Condition | 用以唤醒某节点的线程 |
(3)队列添加、取出元素
//阻塞队列的使用
package Three_ThreadFactory;
import java.util.concurrent.BlockingQueue;
import java.util.concurrent.LinkedBlockingQueue;
public class LinkQueue {
public static void main(String[] args) {
BlockingQueue queue = new LinkedBlockingQueue(5);//声明长度为5的阻塞队列
String s = "我要进队列";
queue.put(s);
queue.take();
//...省略
}
}
方法总览:
方法 | 作用 | 介绍 |
---|---|---|
boolean add(E e) | 添加元素 | 底层调用offer(E e) |
boolean offer(E e) | 添加元素 | 队列满则返回失败,否则获取锁去添加元素 |
boolean affer(E e,long timeout,TimeUnit unit) | 添加元素,超时返回 | 队列未满等同offer;队列已满则等待指定时间,错误则返回 |
boolean put(E e) | 阻塞地添加元素 | 阻塞等待直到完成添加元素 |
E peek() | 取出元素仅 | 仅获取元素不删除 |
E pool() | 取出元素,并删除队列中该元素 | |
E pool(long timeout,timeUnit unit) | 未超时前,取出元素,并删除队列中该元素 | 队列不为空,与pool相同,队列空则等待制定时间否则返回空 |
E take() | 阻塞地取出元素 | 一直阻塞直到取出元素 |
6、线程中的ScheduledExecutorService预执行服务
该类应用于定时执行异步任务、周期性执行异步任务,需要注意异常处理,当抛出异常时,任务将终止周期性执行。
7、java中常用的线程池。
(1)固定线程数量的线程池
//固定线程线程池创建
public static void fixThreadpool() {
ExecutorService theadpool = Executors.newFixedThreadPool(1);
theadpool.execute(()->{
System.out.println("当前线程名字是:" + Thread.currentThread().getName());
});
}
//newFixedThreadPool源码
public static ExecutorService newFixedThreadPool(int nThreads) {
return new ThreadPoolExecutor(nThreads, nThreads,
0L, TimeUnit.MILLISECONDS,
new LinkedBlockingQueue<Runnable>());
}
Executors.newFixedThreadPool创建固定线程线程池,源码中核心线程数和可容载最大线程数相同,当达到核心线程数后,空闲线程不会因超时而被终止或释放,且每添加一个任务后,会将任务添加到工作任务队列,线程池创建一个线程,线程数等于核心线程数时,不会再创建线程。
(2)单线程的线程池
//单线程线程池创建
public static void singleThreadpool() {
ExecutorService theadpool = Executors.newSingleThreadExecutor();
theadpool.execute(()->{
System.out.println("当前线程名字是:" + Thread.currentThread().getName());
});
}
//源码
public static ExecutorService newSingleThreadExecutor() {
return new FinalizableDelegatedExecutorService
(new ThreadPoolExecutor(1, 1,
0L, TimeUnit.MILLISECONDS,
new LinkedBlockingQueue<Runnable>()));
}
通过Executors.newSingleThreadExecutor创建,由源码可知核心线程数和最大线程数一样,且均为1,即只有一个线程在执行队列的工作任务,注意该方法使用了FinalizableDelegatedExecutorService代理来创建线程池。
(3)可缓存的线程池
//可缓存线程线程池创建
public static void cacheThreadpool() {
ExecutorService theadpool = Executors.newCachedThreadPool();
theadpool.execute(()->{
System.out.println("当前线程名字是:" + Thread.currentThread().getName());
});
}
//源码
public static ExecutorService newCachedThreadPool() {
return new ThreadPoolExecutor(0, Integer.MAX_VALUE,
60L, TimeUnit.SECONDS,
new SynchronousQueue<Runnable>());
}
import java.lang.Integer
//@Native public static final int MAX_VALUE = 0x7fffffff;
Executors.newCachedThreadPool创建可缓存的线程池,核心线程数为0,最大线程数为无限大(上面源码课件),空闲线程可以超时存在60s,使用SynchronousQueue同步队列(在添加任务时需同步有另一个工作线程来使用这个任务,否则无法添加)。 (4)定时执行的线程池
//定时执行线程线程池创建
public static void timeThreadpool() {
ExecutorService theadpool = Executors.newScheduledThreadPool(1);
theadpool.execute(()->{
System.out.println("当前线程名字是:" + Thread.currentThread().getName());
});
}
public static ScheduledExecutorService newScheduledThreadPool(int corePoolSize) {
return new ScheduledThreadPoolExecutor(corePoolSize);
}
public ScheduledThreadPoolExecutor(int corePoolSize) {
super(corePoolSize, Integer.MAX_VALUE, 0, NANOSECONDS,
new DelayedWorkQueue());
}
Executors.newScheduledThreadPool创建定时线程,可指定核心线程数,最大线程数为无限大,核心线程数空闲不会超时回收,使用了DelayedWorkQueue延时队列,通过延时队列控制时间执行线程。
8、机票系统多线程+Future运行实例
package Three_ThreadFactory;
import java.util.ArrayList;
import java.util.Random;
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.TimeoutException;
public class imiTicketExample {
public static void main(String[] args) {
//创建核心线程数等同于主机处理器数量的线程池
ExecutorService newFixedThreadPool = Executors.newFixedThreadPool(Runtime.getRuntime().availableProcessors());
int taskNum = 15;//模拟查询多个机票公司的票数
ArrayList<Future<String>> futuresList = new ArrayList<>();
for (int taskindex = 0; taskindex < taskNum; taskindex++) {
//通过Callable创建线程
Callable<String> curcallable = ()->{
//模拟获取到机票过程
long first = System.currentTimeMillis();
long sleep = new Random().nextInt(2500);
Thread.sleep(sleep);
//模拟返回获取到的机票登信息
return (Thread.currentThread().getName()+"卖出" + (System.currentTimeMillis()-first));//获取
};
Future<String> future = newFixedThreadPool.submit(curcallable);
futuresList.add(future);
}
//输出结果
for(Future<String> sFuture : futuresList) {
try {
String string = sFuture.get(3, TimeUnit.SECONDS);//最多等待三秒获取
System.out.println("获取结果:"+string);
} catch (InterruptedException | ExecutionException | TimeoutException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
}
上述算法模拟获取机票售卖公司售卖票的过程,通过Future获取获得的信息,因此简单叙述下Future的运行原理。
Future在主线程阻塞等待时将线程池中的Callable执行结果同步共享变量到FutureTask实现获取信息。Future使用的场景是:会出现主线程阻塞超时等待的场景,不使用场景:只有一个任务,主线程不会出现阻塞等待的场景。