线程池是指管理一组同构工作线程的线程的资源池。
线程池与**工作队列(Work Queue)模切相关,工作队列中保存了所有等待知心的任务。线程池中的工作线程(Work Thread)**的任务很简单:从工作队列中获取一个任务,执行任务,然后返回线程池并等待下一个任务。
使用线程池的好处是:
步骤1:自定义拒绝策略接口
//如果队列已满时的拒绝策略接口
@FunctionalInterface
interface RejectPolicy<T> {
void reject(BlockingQueue<T> blockingQueue, T task);
}
步骤2:自定义任务队列
//工作队列
class BlockingQueue<T> {
//任务列表
private Deque<T> queue = new ArrayDeque<>();
//锁
private ReentrantLock lock = new ReentrantLock();
//生产者条件变量
private Condition fullWaitSet = lock.newCondition();
//消费者条件变量
private Condition emptyWaitSet = lock.newCondition();
//容量
private int capacity;
public BlockingQueue(int capacity) {
this.capacity = capacity;
}
//阻塞获取
public T take() {
lock.lock();
try {
//队列为空时等待
while (queue.isEmpty()) {
try {
emptyWaitSet.await();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
//不为空时,从队列中获取
T t = queue.removeFirst();
fullWaitSet.signalAll();
return t;
} finally {
lock.unlock();
}
}
//带超时的阻塞获取
public T take(long timeout, TimeUnit unit) {
lock.lock();
try {
//剩余等待时间(纳秒)
long nanos = unit.toNanos(timeout);
while (queue.isEmpty()) {
try {
if (nanos <= 0) {
return null;
}
//awaitNanos返回剩余等待时间,防止虚假唤醒再次等待timeout时间
nanos = emptyWaitSet.awaitNanos(nanos);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
T t = queue.removeFirst();
fullWaitSet.signalAll();
return t;
} finally {
lock.unlock();
}
}
//阻塞添加
public void put(T t) {
lock.lock();
try {
//队列满时等待进入
while (queue.size() >= capacity) {
try {
System.out.println("等待加入任务队列" + t);
fullWaitSet.await();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
//队列不满时直接进入任务队列
System.out.println("加入任务队列" + t);
queue.addLast(t);
emptyWaitSet.signalAll();
} finally {
lock.unlock();
}
}
//带超时时间的阻塞添加
public boolean put(T t, long timeout, TimeUnit timeUnit) {
lock.lock();
try {
//等待进入任务队列的超时时间
long nanos = timeUnit.toNanos(timeout);
while (queue.size() >= capacity) {
try {
System.out.println("等待加入任务队列" + t);
if (nanos <= 0) {
return false;
}
nanos = fullWaitSet.awaitNanos(nanos);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
System.out.println("加入任务队列" + t);
queue.addLast(t);
emptyWaitSet.signalAll();
return true;
} finally {
lock.unlock();
}
}
//获取队列大小
public int size() {
lock.lock();
try {
return queue.size();
} finally {
lock.unlock();
}
}
//执行用户传入的拒绝策略
public void tryPut(RejectPolicy<T> rejectPolicy, T task) {
lock.lock();
try {
//判断队列是否已满
if (queue.size() >= capacity) {
//执行用户传入的拒绝策略
rejectPolicy.reject(this, task);
} else { //有空闲,直接加入任务队列
System.out.println("加入任务队列 " + task);
queue.add(task);
emptyWaitSet.signalAll();
}
} finally {
lock.unlock();
}
}
}
步骤3:自定义线程池
//自定义线程池
class ThreadPool {
//工作队列
private BlockingQueue<Runnable> taskQueue;
//线程集合
private HashSet<Worker> workers = new HashSet<>();
//核心线程的数目
private int coreSize;
//获得任务的超时时间
private long timeout;
//拒绝策略
private RejectPolicy<Runnable> rejectPolicy;
//时间单位
private TimeUnit timeUnit;
public ThreadPool(int coreSize, long timeout, TimeUnit timeUnit, int taskCapacity, RejectPolicy<Runnable> rejectPolicy) {
this.coreSize = coreSize;
this.timeout = timeout;
this.timeUnit = timeUnit;
this.rejectPolicy = rejectPolicy;
taskQueue = new BlockingQueue<>(taskCapacity);
}
public void execute(Runnable task) {
synchronized (workers) {
//当任务数没有超过coreSize,则直接创建worker并将任务交给其执行
if (workers.size() < coreSize) {
Worker worker = new Worker(task);
System.out.println("新增worker" + worker + " " + task);
workers.add(worker);
worker.start();
} else {
//如果任务数超过coreSize,则将其放入任务队列等待
// taskQueue.put(task);
/**
* 如果队列已满时的策略
* 1、死等
* 2、带超时的等待
* 3、让调用者放弃执行任务
* 4、让调用者抛出异常
* 5、让调用者自己执行任务
*/
taskQueue.tryPut(rejectPolicy, task);
}
}
}
//工作线程类
class Worker extends Thread {
private Runnable task;
public Worker(Runnable task) {
this.task = task;
}
//执行任务
@Override
public void run() {
//1.如果task不为空,则执行task
//2.当task执行完毕,要从任务队列中获取任务并执行
while (task != null || (task = taskQueue.take(timeout, timeUnit)) != null) {
try {
System.out.println(Thread.currentThread() + " 正在执行" + task);
task.run();
} catch (Exception e) {
e.printStackTrace();
} finally {
//task执行完之后,从任务队列中获取任务
task = null;
}
}
//当前任务都执行完毕之后,将自身从队列中移除
synchronized (workers) {
System.out.println(Thread.currentThread() + " 执行完毕被移除");
workers.remove(this);
}
}
}
}
步骤4:测试
public static void main(String[] args) {
ThreadPool threadPool = new ThreadPool(
1, 1000, TimeUnit.MILLISECONDS, 1, (queue, task) -> {
//1、死等
queue.put(task);
//2、带超时的等待
// boolean put = queue.put(task, 2000, TimeUnit.MILLISECONDS);
// System.out.println("等待执行 " + (put ? "成功" : "失败"));
//3、让调用者放弃执行任务
// System.out.println("放弃执行任务 " + task);
//4、让调用者抛出异常
// throw new RuntimeException("任务执行失败:" + task);
//5、让调用者自己执行任务
// task.run();
});
//使用自定义线程池执行线程
for (int i = 0; i < 3; i++) {
int finalI = i;
threadPool.execute(() -> {
System.out.println("task " + finalI + " runing....");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
});
}
}
ThreadPoolExecutor类的继承关系:
ThreadPoolExecutor 使用 int 的高 3 位来表示线程池状态,低 29 位表示线程数量
状态名 | 高3位 | 接受新任务 | 处理阻塞队列任务 | 说明 |
---|---|---|---|---|
RUNNING | 111 | Y | Y | — |
SHUTDOWN | 000 | N | Y | 不会接收新任务,但会处理阻塞队列剩余 任务 |
STOP | 001 | N | N | 会中断正在执行的任务,并抛弃阻塞队列 任务 |
TIDYING | 010 | — | — | 任务全执行完毕,活动线程为 0 即将进入 终结 |
TERMINATED | 011 | — | — | 终结状态 |
从数字大小上比较,TERMINATED > TIDYING > STOP > SHUTDOWN > RUNNING
这些信息存储在一个原子变量 ctl 中,目的是将线程池状态与线程个数合二为一,这样就可以用一次 cas 原子操作 进行赋值
// c 为旧值, ctlOf 返回结果为新值
ctl.compareAndSet(c, ctlOf(targetState, workerCountOf(c))));
// rs 为高 3 位代表线程池状态, wc 为低 29 位代表线程个数,ctl 使用位或合并它们
private static int ctlOf(int rs, int wc) {
return rs | wc;
}
public ThreadPoolExecutor(int corePoolSize,
int maximumPoolSize,
long keepAliveTime,
TimeUnit unit,
BlockingQueue<Runnable> workQueue,
ThreadFactory threadFactory,
RejectedExecutionHandler handler) {
参数介绍:
当任务阻塞队列满时,线程池不会直接调用 拒绝策略 ,而是创建 救济线程 对超出的任务进行执行。执行完毕救济线程就会进入终结状态。如果当前救济线程也全部被使用,那么才会调用拒绝策略。
工作方式:
maximumPoolSize - corePoolSize
数目的救济线程救急根据这个构造方法,JDK Executors 类中提供了众多工厂方法来创建各种用途的线程池
主要阻塞队列:
public static ExecutorService newFixedThreadPool(int nThreads, ThreadFactory threadFactory) {
return new ThreadPoolExecutor(nThreads, nThreads,
0L, TimeUnit.MILLISECONDS,
new LinkedBlockingQueue<Runnable>(),
threadFactory);
}
特点:
使用:
public static void main(String[] args) {
ExecutorService executorService = Executors.newFixedThreadPool(2, new ThreadFactory() {
//ThreadFactory用于给线程起名字
private AtomicInteger t = new AtomicInteger(1);
@Override
public Thread newThread(@NotNull Runnable r) {
return new Thread(r,"我的线程"+t.getAndIncrement());
}
});
executorService.execute(()->{
TheadPrint.print("1");
});
executorService.execute(()->{
TheadPrint.print("2");
});
executorService.execute(()->{
TheadPrint.print("3");
});
}
结果:
12:33:17:038 Thread[我的线程2,5,main]: 2
12:33:17:040 Thread[我的线程2,5,main]: 3
12:33:17:047 Thread[我的线程1,5,main]: 1
其中ThreadFactory主要用来更改线程的名字,可以省略
public static ExecutorService newCachedThreadPool() {
return new ThreadPoolExecutor(0, Integer.MAX_VALUE,
60L, TimeUnit.SECONDS,
new SynchronousQueue<Runnable>());
}
特点:
Integer.MAX_VALUE
,救急线程的空闲生存时间是 60s,意味着
public static void main(String[] args) throws InterruptedException {
SynchronousQueue<Integer> integers = new SynchronousQueue<>();
new Thread(() -> {
try {
TheadPrint.print("putting... "+ 1);
integers.put(1);
TheadPrint.print("putted..." + 1);
TheadPrint.print("putting... "+ 2);
integers.put(2);
TheadPrint.print("putted..." + 2);
} catch (InterruptedException e) {
e.printStackTrace();
}
}, "t1").start();
Thread.sleep(1000);
new Thread(() -> {
try {
TheadPrint.print("taking " + 1);
integers.take();
} catch (InterruptedException e) {
e.printStackTrace();
}
}, "t2").start();
Thread.sleep(1000);
new Thread(() -> {
try {
TheadPrint.print("taking " + 2);
integers.take();
} catch (InterruptedException e) {
e.printStackTrace();
}
}, "t3").start();
}
结果:
12:52:41:123 Thread[t1,5,main]: putting... 1
12:52:42:037 Thread[t2,5,main]: taking 1
12:52:42:042 Thread[t1,5,main]: putted...1
12:52:42:042 Thread[t1,5,main]: putting... 2
12:52:43:038 Thread[t3,5,main]: taking 2
12:52:43:038 Thread[t1,5,main]: putted...2
public static ExecutorService newSingleThreadExecutor() {
return new FinalizableDelegatedExecutorService
(new ThreadPoolExecutor(1, 1,
0L, TimeUnit.MILLISECONDS,
new LinkedBlockingQueue<Runnable>()));
}
使用场景:
希望多个任务排队执行。线程数固定为 1,任务数多于 1 时,会放入无界队列排队。任务执行完毕,这唯一的线程 也不会被释放。
区别:
1)execute
// 执行任务
void execute(Runnable command);
2)submit
带返回结果的调用,返回结果使用Future接收(使用了保护暂停模式)
// 提交任务 task,用返回值 Future 获得任务执行结果
<T> Future<T> submit(Callable<T> task);
使用示例:
ExecutorService executorService = Executors.newFixedThreadPool(2);
Future<String> future = executorService.submit(new Callable<String>() {
@Override
public String call() throws Exception {
TheadPrint.print("run...");
Thread.sleep(1000);
return "Hello World!";
}
});
TheadPrint.print(future.get());
结果:
13:22:54:092 Thread[pool-1-thread-1,5,main]: run...
13:22:55:094 Thread[main,5,main]: Hello World!
3)invokeAll
带返回结果的调用,并且可以返回多个结果
// 提交 tasks 中所有任务
<T> List<Future<T>> invokeAll(Collection<? extends Callable<T>> tasks) throws InterruptedException;
// 提交 tasks 中所有任务,带超时时间
<T> List<Future<T>> invokeAll(Collection<? extends Callable<T>> tasks, long timeout, TimeUnit unit) throws InterruptedException;
示例:
public static void main(String[] args) throws ExecutionException, InterruptedException {
ExecutorService executorService = Executors.newFixedThreadPool(2);
List<Future<String>> futureList = executorService.invokeAll(Arrays.asList(
() -> {
TheadPrint.print("1");
Thread.sleep(1000);
return "1";
},
() -> {
TheadPrint.print("2");
Thread.sleep(1000);
return "2";
},
() -> {
TheadPrint.print("3");
Thread.sleep(1000);
return "3";
}
));
futureList.forEach((f) -> {
try {
TheadPrint.print(f.get());
} catch (InterruptedException e) {
e.printStackTrace();
} catch (ExecutionException e) {
e.printStackTrace();
}
});
}
结果:
15:01:06:860 Thread[pool-1-thread-2,5,main]: 2
15:01:06:860 Thread[pool-1-thread-1,5,main]: 1
15:01:07:862 Thread[pool-1-thread-2,5,main]: 3
15:01:08:872 Thread[main,5,main]: 1
15:01:08:872 Thread[main,5,main]: 2
15:01:08:873 Thread[main,5,main]: 3
4)invokeAny
带返回结果的调用,只返回最先执行完毕的那一个线程的结果
// 提交 tasks 中所有任务,哪个任务先成功执行完毕,返回此任务执行结果,其它任务取消
<T> T invokeAny(Collection<? extends Callable<T>> tasks)
throws InterruptedException, ExecutionException;
// 提交 tasks 中所有任务,哪个任务先成功执行完毕,返回此任务执行结果,其它任务取消,带超时时间
<T> T invokeAny(Collection<? extends Callable<T>> tasks, long timeout, TimeUnit unit) throws InterruptedException, ExecutionException, TimeoutException;
示例:
public static void main(String[] args) throws ExecutionException, InterruptedException {
ExecutorService executorService = Executors.newFixedThreadPool(1);
String str = executorService.invokeAny(Arrays.asList(
() -> {
TheadPrint.print("1");
Thread.sleep(2000);
TheadPrint.print("1");
return "1";
},
() -> {
TheadPrint.print("2");
Thread.sleep(2000);
TheadPrint.print("2");
return "2";
},
() -> {
TheadPrint.print("3");
Thread.sleep(2000);
TheadPrint.print("3");
return "3";
}
));
TheadPrint.print(str);
}
结果:
15:12:48:309 Thread[pool-1-thread-1,5,main]: 1
15:12:50:319 Thread[pool-1-thread-1,5,main]: 1
15:12:50:320 Thread[pool-1-thread-1,5,main]: 2
15:12:50:321 Thread[main,5,main]: 1
/*
线程池状态变为 SHUTDOWN
- 不会接收新任务
- 但已提交任务会执行完,包括阻塞队列中的任务
- 此方法不会阻塞调用线程的执行
*/
void shutdown();
具体实现:
public void shutdown() {
final ReentrantLock mainLock = this.mainLock;
mainLock.lock();
try {
checkShutdownAccess();
// 修改线程池状态
advanceRunState(SHUTDOWN);
// 仅会打断空闲线程
interruptIdleWorkers();
onShutdown(); // hook for ScheduledThreadPoolExecutor
} finally {
mainLock.unlock();
}
// 尝试终结(没有运行的线程可以立刻终结,如果还有运行的线程也不会等)
tryTerminate();
}
使用示例:
public static void main(String[] args) throws ExecutionException, InterruptedException {
ExecutorService executorService = Executors.newFixedThreadPool(2);
executorService.submit(
() -> {
TheadPrint.print("begin 1....");
Thread.sleep(1000);
TheadPrint.print("end 1....");
return "1";
}
);
executorService.submit(
() -> {
TheadPrint.print("begin 2....");
Thread.sleep(1000);
TheadPrint.print("end 2....");
return "1";
}
);
executorService.submit(
() -> {
TheadPrint.print("begin 3....");
Thread.sleep(1000);
TheadPrint.print("end 3....");
return "1";
}
);
TheadPrint.print("shutdown...");
executorService.shutdown();
}
下面是程序的运行结果,可以看出所有任务都执行完毕了,虽然任务3会被放入阻塞队列,但是仍然会被执行
15:58:57:687 Thread[pool-1-thread-1,5,main]: begin 1....
15:58:57:750 Thread[pool-1-thread-2,5,main]: begin 2....
15:58:57:756 Thread[main,5,main]: shutdown...
15:58:58:691 Thread[pool-1-thread-1,5,main]: end 1....
15:58:58:694 Thread[pool-1-thread-1,5,main]: begin 3....
15:58:58:753 Thread[pool-1-thread-2,5,main]: end 2....
15:58:59:719 Thread[pool-1-thread-1,5,main]: end 3....
如果在shutdown之后再调用任务执行,就会抛出异常:
TheadPrint.print("shutdown...");
executorService.shutdown();
executorService.submit(
() -> {
TheadPrint.print("begin 4....");
Thread.sleep(1000);
TheadPrint.print("end 4....");
return "1";
}
);
结果
16:04:27:232 Thread[pool-1-thread-2,5,main]: begin 2....
16:04:27:237 Thread[main,5,main]: shutdown...
Exception in thread "main" java.util.concurrent.RejectedExecutionException: Task java.util.concurrent.FutureTask@3f91beef rejected from java.util.concurrent.ThreadPoolExecutor@1a6c5a9e[Shutting down, pool size = 2, active threads = 2, queued tasks = 1, completed tasks = 0]
at java.util.concurrent.ThreadPoolExecutor$AbortPolicy.rejectedExecution(ThreadPoolExecutor.java:2047)
at java.util.concurrent.ThreadPoolExecutor.reject(ThreadPoolExecutor.java:823)
at java.util.concurrent.ThreadPoolExecutor.execute(ThreadPoolExecutor.java:1369)
at java.util.concurrent.AbstractExecutorService.submit(AbstractExecutorService.java:134)
at concurrent.ch8_tools.ThreadPoolExecutorTest.main(ThreadPoolExecutorTest.java:150)
16:04:27:255 Thread[pool-1-thread-1,5,main]: begin 1....
另外,调用showdown之后主线程会继续运行,如果想等待任务执行结束需要调用下面的方法
//等待任务执行结束,而且最多等待10秒
executorService.awaitTermination(10, TimeUnit.SECONDS);
/* 线程池状态变为 STOP
- 不会接收新任务
- 会将队列中的任务返回
- 并用 interrupt 的方式中断正在执行的任务
*/
List<Runnable> 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;
}
使用:
public static void main(String[] args) throws ExecutionException, InterruptedException {
ExecutorService executorService = Executors.newFixedThreadPool(2);
executorService.submit(
() -> {
TheadPrint.print("begin 1....");
Thread.sleep(1000);
TheadPrint.print("end 1....");
return "1";
}
);
executorService.submit(
() -> {
TheadPrint.print("begin 2....");
Thread.sleep(1000);
TheadPrint.print("end 2....");
return "1";
}
);
executorService.submit(
() -> {
TheadPrint.print("begin 3....");
Thread.sleep(1000);
TheadPrint.print("end 3....");
return "1";
}
);
TheadPrint.print("shutdownNow...");
List<Runnable> runnables = executorService.shutdownNow();
TheadPrint.print("没有执行的任务个数:"+runnables.size());
}
输出结果如下,从结果可以看出,任务1、2都只是刚开始而没有被执行完毕。而在阻塞队列中的任务3直接被返回了。
16:12:11:324 Thread[main,5,main]: shutdownNow...
16:12:11:326 Thread[main,5,main]: 没有执行的任务个数:1
16:12:11:328 Thread[pool-1-thread-1,5,main]: begin 1....
16:12:11:325 Thread[pool-1-thread-2,5,main]: begin 2....
// 不在 RUNNING 状态的线程池,此方法就返回 true
boolean isShutdown();
// 线程池状态是否是 TERMINATED
boolean isTerminated();
// 调用 shutdown 后,由于调用线程并不会等待所有任务运行结束,因此如果它想在线程池 TERMINATED 后做些事 情,可以利用此方法等待
boolean awaitTermination(long timeout, TimeUnit unit) throws InterruptedException;
让有限的工作线程(Worker Thread)来轮流异步处理无限多的任务。也可以将其归类为分工模式,它的典型实现 就是线程池,也体现了经典设计模式中的享元模式。
例如,海底捞的服务员(线程),轮流处理每位客人的点餐(任务),如果为每位客人都配一名专属的服务员,那 么成本就太高了(对应另一种多线程设计模式:Thread-Per-Message)
注意,不同任务类型应该使用不同的线程池,这样能够避免饥饿,并能提升效率
固定大小线程池会有饥饿现象
客人点餐:必须先点完餐,等菜做好,上菜,在此期间处理点餐的工人必须等待
后厨做菜:没啥说的,做就是了
示例代码:
public class ThreadDeadLockTest {
static final List<String> MENU = Arrays.asList("地三鲜", "宫保鸡丁", "辣子鸡丁", "烤鸡翅");
static Random RANDOM = new Random();
static String cooking() {
return MENU.get(RANDOM.nextInt(MENU.size()));
}
public static void main(String[] args) {
ExecutorService executorService = Executors.newFixedThreadPool(2);
executorService.execute(() -> {
TheadPrint.print("处理点餐...");
Future<String> f = executorService.submit(() -> {
TheadPrint.print("做菜");
return cooking();
});
try {
TheadPrint.print("上菜: " + f.get());
} catch (InterruptedException | ExecutionException e) {
e.printStackTrace();
}
});
/*executorService.execute(() -> {
TheadPrint.print("处理点餐...");
Future f = executorService.submit(() -> {
TheadPrint.print("做菜");
return cooking();
});
try {
TheadPrint.print("上菜: "+f.get());
} catch (InterruptedException | ExecutionException e) {
e.printStackTrace();
}
});*/
}
}
执行结果:
16:45:46:112 Thread[pool-1-thread-1,5,main]: 处理点餐...
16:45:46:126 Thread[pool-1-thread-2,5,main]: 做菜
16:45:46:126 Thread[pool-1-thread-1,5,main]: 上菜: 烤鸡翅
当放开注释之后,可能的结果如下,出现了饥饿。这是因为两个线程都在等待另外的线程进行做菜工作,但两个线程都被占用了,所以出现这种情况。
16:43:10:373 Thread[pool-1-thread-2,5,main]: 处理点餐...
16:43:10:382 Thread[pool-1-thread-1,5,main]: 处理点餐...
解决方法可以增加线程池的大小,不过不是根本解决方案,还是前面提到的,不同的任务类型,采用不同的线程 池,例如:
public class ThreadDeadLockTest {
static final List<String> MENU = Arrays.asList("地三鲜", "宫保鸡丁", "辣子鸡丁", "烤鸡翅");
static Random RANDOM = new Random();
static String cooking() {
return MENU.get(RANDOM.nextInt(MENU.size()));
}
public static void main(String[] args) {
ExecutorService waiterPool = Executors.newFixedThreadPool(1);
ExecutorService cookerPool = Executors.newFixedThreadPool(1);
waiterPool.execute(() -> {
TheadPrint.print("处理点餐...");
Future<String> f = cookerPool.submit(() -> {
TheadPrint.print("做菜");
return cooking();
});
try {
TheadPrint.print("上菜: " + f.get());
} catch (InterruptedException | ExecutionException e) {
e.printStackTrace();
}
});
waiterPool.execute(() -> {
TheadPrint.print("处理点餐...");
Future<String> f = cookerPool.submit(() -> {
TheadPrint.print("做菜");
return cooking();
});
try {
TheadPrint.print("上菜: "+f.get());
} catch (InterruptedException | ExecutionException e) {
e.printStackTrace();
}
});
}
}
输出如下,这样就不会出现饥饿的问题:
16:48:14:594 Thread[pool-1-thread-1,5,main]: 处理点餐...
16:48:14:611 Thread[pool-2-thread-1,5,main]: 做菜
16:48:14:612 Thread[pool-1-thread-1,5,main]: 上菜: 烤鸡翅
16:48:14:612 Thread[pool-1-thread-1,5,main]: 处理点餐...
16:48:14:613 Thread[pool-2-thread-1,5,main]: 做菜
16:48:14:614 Thread[pool-1-thread-1,5,main]: 上菜: 烤鸡翅
CPU 密集型运算
通常采用cpu 核数 + 1
能够实现最优的 CPU 利用率,+1 是保证当线程由于页缺失故障(操作系统)或其它原因 导致暂停时,额外的这个线程就能顶上去,保证 CPU 时钟周期不被浪费
I/O 密集型运算
CPU 不总是处于繁忙状态,例如,当你执行业务计算时,这时候会使用 CPU 资源,但当你执行 I/O 操作时、远程 RPC 调用时,包括进行数据库操作时,这时候 CPU 就闲下来了,你可以利用多线程提高它的利用率。
经验公式如下
线程数 = 核数 * 期望 CPU 利用率 * 总时间(CPU计算时间+等待时间) / CPU 计算时间
例如 4 核 CPU 计算时间是 50% ,其它等待时间是 50%,期望 cpu 被 100% 利用,套用公式
4 * 100% * 100% / 50% = 8
例如 4 核 CPU 计算时间是 10% ,其它等待时间是 90%,期望 cpu 被 100% 利用,套用公式
4 * 100% * 100% / 10% = 40
在『任务调度线程池』功能加入之前,可以使用 java.util.Timer 来实现定时功能,Timer 的优点在于简单易用,但 由于所有任务都是由同一个线程来调度,因此所有任务都是串行执行的,同一时间只能有一个任务在执行,前一个 任务的延迟或异常都将会影响到之后的任务:
public static void main (String[]args){
Timer timer = new Timer();
TimerTask task1 = new TimerTask() {
@Override
public void run() {
log.debug("task 1");
sleep(2);
}
};
TimerTask task2 = new TimerTask() {
@Override
public void run() {
log.debug("task 2");
}
};
// 使用 timer 添加两个任务,希望它们都在 1s 后执行
// 但由于 timer 内只有一个线程来顺序执行队列中的任务,因此『任务1』的延时,影响了『任务2』的执行
timer.schedule(task1, 1000);
timer.schedule(task2, 1000);
}
输出如下所示,可以看出task2是在task1执行完毕之后执行的
20:46:09.444 c.TestTimer [main] - start...
20:46:10.447 c.TestTimer [Timer-0] - task 1
20:46:12.448 c.TestTimer [Timer-0] - task 2
使用 ScheduledExecutorService 改写:
public static void main(String[] args) {
ScheduledExecutorService pool = Executors.newScheduledThreadPool(2);
pool.schedule(() -> {
TheadPrint.print("任务1...");
int i = 1 / 0;
}, 1, TimeUnit.SECONDS);
pool.schedule(() -> {
TheadPrint.print("任务2...");
}, 1, TimeUnit.SECONDS);
}
结果如下,可以看出两个任务几乎是同时执行的,而且任务1也没有被一场打断
18:21:19:360 Thread[pool-1-thread-1,5,main]: 任务1...
18:21:19:364 Thread[pool-1-thread-2,5,main]: 任务2...
整个线程池表现为:线程数固定,任务数多于线程数时,会放入无界队列排队。任务执行完毕,这些线 程也不会被释放。用来执行延迟或反复执行的任务
使用scheduleAtFixedRate函数可以实现周期执行:
ScheduledExecutorService pool = Executors.newScheduledThreadPool(2);
TheadPrint.print("开始执行任务");
pool.scheduleAtFixedRate(() -> {
TheadPrint.print("任务1...");
//开始时间,间隔时间,时间单位
}, 1, 1,TimeUnit.SECONDS);
结果如下所示,上面的代码实现了一秒后开始执行,之后每隔一秒执行一次
18:53:56:407 Thread[main,5,main]: 开始执行任务
18:53:57:622 Thread[pool-1-thread-1,5,main]: 任务1...
18:53:58:621 Thread[pool-1-thread-1,5,main]: 任务1...
18:53:59:641 Thread[pool-1-thread-2,5,main]: 任务1...
需要注意的是,该函数如果任务执行时间超过间隔时间,那么就不会继续等待间隔时间,测试代码如下:
ScheduledExecutorService pool = Executors.newScheduledThreadPool(2);
TheadPrint.print("开始执行任务");
pool.scheduleAtFixedRate(() -> {
TheadPrint.print("任务1...");
try {
Thread.sleep(3000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}, 1, 1,TimeUnit.SECONDS);
可以看到每个任务执行时间的间隔为3秒,而不是4秒,说明任务执行完之后并没等待间隔时间
18:56:30:516 Thread[main,5,main]: 开始执行任务
18:56:31:690 Thread[pool-1-thread-1,5,main]: 任务1...
18:56:34:694 Thread[pool-1-thread-1,5,main]: 任务1...
18:56:37:695 Thread[pool-1-thread-1,5,main]: 任务1...
2)scheduleWithFixedDelay函数
和上面不同的是,scheduleWithFixedDelay函数,就算任务执行时间超过间隔时间,仍然会继续等待间隔时间,测试代码如下:
ScheduledExecutorService pool = Executors.newScheduledThreadPool(2);
TheadPrint.print("开始执行任务");
pool.scheduleWithFixedDelay(() -> {
TheadPrint.print("任务1...");
try {
Thread.sleep(3000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}, 1, 1,TimeUnit.SECONDS);
结果如下,可以看出确实等待了4秒而不是3秒
19:01:24:926 Thread[main,5,main]: 开始执行任务
19:01:26:225 Thread[pool-1-thread-1,5,main]: 任务1...
19:01:30:230 Thread[pool-1-thread-1,5,main]: 任务1...
19:01:34:265 Thread[pool-1-thread-1,5,main]: 任务1...
之前我们已经演示过了如果任务执行过程出现异常,ScheduledExecutorService中的其它任务不会被打断。但是ScheduledExecutorService也没有做任何处理,例如输出控制台。这时就需要我们自己对异常进行处理
示例代码:
ScheduledExecutorService pool = Executors.newScheduledThreadPool(2);
TheadPrint.print("开始执行任务");
pool.execute(()->{
TheadPrint.print("任务1...");
try {
int i = 1 / 0;
} catch (Exception e) {
e.printStackTrace();
}
});
pool.execute(()->{
TheadPrint.print("任务2...");
});
结果:
19:32:29:000 Thread[main,5,main]: 开始执行任务
19:32:29:705 Thread[pool-1-thread-1,5,main]: 任务1...
19:32:29:713 Thread[pool-1-thread-1,5,main]: 任务2...
java.lang.ArithmeticException: / by zero
at concurrent.ch8_tools.ThreadPoolExecutorTest.lambda$main$0(ThreadPoolExecutorTest.java:162)
at java.util.concurrent.Executors$RunnableAdapter.call(Executors.java:511)
at java.util.concurrent.FutureTask.run(FutureTask.java:266)
at java.util.concurrent.ScheduledThreadPoolExecutor$ScheduledFutureTask.access$201(ScheduledThreadPoolExecutor.java:180)
at java.util.concurrent.ScheduledThreadPoolExecutor$ScheduledFutureTask.run(ScheduledThreadPoolExecutor.java:293)
at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1142)
at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:617)
at java.lang.Thread.run(Thread.java:745)
Future不仅能够接收返回的结果,如果出现异常,那么它接收的就是异常,示例代码如下:
public static void main(String[] args) throws ExecutionException, InterruptedException {
ScheduledExecutorService pool = Executors.newScheduledThreadPool(2);
TheadPrint.print("开始执行任务");
Future<String> f = pool.submit(new Callable<String>() {
@Override
public String call() throws Exception {
TheadPrint.print("任务1...");
int i = 1 / 0;
return "成功";
}
});
TheadPrint.print(f.get());
}
结果:
19:38:09:757 Thread[main,5,main]: 开始执行任务
19:38:09:922 Thread[pool-1-thread-1,5,main]: 任务1...
Exception in thread "main" java.util.concurrent.ExecutionException: java.lang.ArithmeticException: / by zero
at java.util.concurrent.FutureTask.report(FutureTask.java:122)
at java.util.concurrent.FutureTask.get(FutureTask.java:192)
at concurrent.ch8_tools.ThreadPoolExecutorTest.main(ThreadPoolExecutorTest.java:167)
Caused by: java.lang.ArithmeticException: / by zero
at concurrent.ch8_tools.ThreadPoolExecutorTest$1.call(ThreadPoolExecutorTest.java:162)
at concurrent.ch8_tools.ThreadPoolExecutorTest$1.call(ThreadPoolExecutorTest.java:158)
at java.util.concurrent.FutureTask.run(FutureTask.java:266)
at java.util.concurrent.ScheduledThreadPoolExecutor$ScheduledFutureTask.access$201(ScheduledThreadPoolExecutor.java:180)
at java.util.concurrent.ScheduledThreadPoolExecutor$ScheduledFutureTask.run(ScheduledThreadPoolExecutor.java:293)
at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1142)
at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:617)
at java.lang.Thread.run(Thread.java:745)
Tomcat 在哪里用到了线程池呢
Tomcat 线程池扩展了 ThreadPoolExecutor,行为稍有不同
public void execute(Runnable command, long timeout, TimeUnit unit) {
submittedCount.incrementAndGet();
try {
//尝试执行任务,如果超出maximumPoolSize 会抛出RejectedExecutionException
super.execute(command);
} catch (RejectedExecutionException rx) {
if (super.getQueue() instanceof TaskQueue) {
final TaskQueue queue = (TaskQueue) super.getQueue();
try {
//Tomcat对异常进行了捕获,再次尝试将任务放入队列
if (!queue.force(command, timeout, unit)) {
//如果还失败,才抛出 RejectedExecutionException 异常
submittedCount.decrementAndGet();
throw new RejectedExecutionException("Queue capacity is full.");
}
} catch (InterruptedException x) {
submittedCount.decrementAndGet();
Thread.interrupted();
throw new RejectedExecutionException(x);
}
} else {
submittedCount.decrementAndGet();
throw rx;
}
}
}
Connector 配置
配置项 | 默认值 | 说明 |
---|---|---|
acceptorThreadCount | 1 | acceptor 线程数量 |
pollerThreadCount | 1 | poller 线程数量 |
minSpareThreads | 10 | 核心线程数,即 corePoolSize |
maxThreads | 200 | 大线程数,即 maximumPoolSize |
executor | — | Executor 名称,用来引用下面的 Executor配置 |
Executor 线程配置
配置项 | 默认值 | 说明 |
---|---|---|
threadPriority | 5 | 线程优先级 |
daemon | true | 是否守护线程 |
minSpareThreads | 25 | 核心线程数,即 corePoolSize |
maxThreads | 200 | 最大线程数,即 maximumPoolSize |
maxIdleTime | 60000 | 线程生存时间,单位是毫秒,默认值即 1 分钟 |
maxQueueSize | Integer.MAX_VALUE | 队列长度,默认无界 |
prestartminSpareThreads | false | 核心线程是否在服务器启动时启动 |
Tomcat中的阻塞队列默认是无界的,那么如果按照Java线程池原有的规则,救济线程是永远用不到的。所以Tomcat更改了救济线程和阻塞队列的使用规则,规则如下:
Fork/Join 是 JDK 1.7 加入的新的线程池实现,它体现的是一种分治思想,适用于能够进行任务拆分的 cpu 密集型 运算
所谓的任务拆分,是将一个大任务拆分为算法上相同的小任务,直至不能拆分可以直接求解。跟递归相关的一些计 算,如归并排序、斐波那契数列、都可以用分治思想进行求解
Fork/Join 在分治的基础上加入了多线程,可以把每个任务的分解和合并交给不同的线程来完成,进一步提升了运 算效率
Fork/Join 默认会创建与 cpu 核心数大小相同的线程池
提交给 Fork/Join 线程池的任务需要继承 RecursiveTask(有返回值)或 RecursiveAction(没有返回值),例如下 面定义了一个对 1~n 之间的整数求和的任务
class MyTask extends RecursiveTask<Integer> {
private Integer n;
public MyTask(Integer n) {
this.n = n;
}
@Override
protected Integer compute() {
//设置终止条件
if(n == 1){
TheadPrint.print("join() " + n);
return 1;
}
//拆分任务
MyTask t = new MyTask(n - 1);
//执行任务
t.fork();
TheadPrint.print("fork() " + n + " + " + t.n);
//获取任务结果
Integer res = t.join() + n;
TheadPrint.print("join() " + n + " + " + t.n + " = " + res);
return res;
}
@Override
public String toString() {
return "MyTask{" +
"n=" + n +
'}';
}
}
测试:
public static void main(String[] args) {
ForkJoinPool pool = new ForkJoinPool(4);
System.out.println(pool.invoke(new MyTask(5)));
}
结果:
12:02:57:892 Thread[ForkJoinPool-1-worker-0,5,main]: fork() 2 + 1
12:02:57:894 Thread[ForkJoinPool-1-worker-0,5,main]: join() 1
12:02:57:895 Thread[ForkJoinPool-1-worker-0,5,main]: join() 2 + 1 = 3
12:02:57:895 Thread[ForkJoinPool-1-worker-3,5,main]: fork() 3 + 2
12:02:57:895 Thread[ForkJoinPool-1-worker-3,5,main]: join() 3 + 2 = 6
12:02:57:896 Thread[ForkJoinPool-1-worker-1,5,main]: fork() 5 + 4
12:02:57:917 Thread[ForkJoinPool-1-worker-2,5,main]: fork() 4 + 3
12:02:57:920 Thread[ForkJoinPool-1-worker-2,5,main]: join() 4 + 3 = 10
12:02:57:921 Thread[ForkJoinPool-1-worker-1,5,main]: join() 5 + 4 = 15
15
用图来表示如下图,虽然上图的打印顺序不是顺序的,但是执行的时候仍是顺序执行的,如t1需要等待t2执行完毕才能得到结果。
因此任务切分十分关键,下面就是求和的改进
class AddTask3 extends RecursiveTask<Integer> {
int begin;
int end;
public AddTask3(int begin, int end) {
this.begin = begin;
this.end = end;
}
@Override
public String toString() {
return "{" + begin + "," + end + '}';
}
@Override
protected Integer compute() {
if (begin == end) {
log.debug("join() {}", begin);
return begin;
}
if (end - begin == 1) {
log.debug("join() {} + {} = {}", begin, end, end + begin);
return end + begin;
}
int mid = (end + begin) / 2;
AddTask3 t1 = new AddTask3(begin, mid);
t1.fork();
AddTask3 t2 = new AddTask3(mid + 1, end);
t2.fork();
log.debug("fork() {} + {} = ?", t1, t2);
int result = t1.join() + t2.join();
log.debug("join() {} + {} = {}", t1, t2, result);
return result;
}
}
然后提交给 ForkJoinPool 来执行
public static void main(String[] args) {
ForkJoinPool pool = new ForkJoinPool(4);
System.out.println(pool.invoke(new AddTask3(1, 10)));
}
结果
[ForkJoinPool-1-worker-0] - join() 1 + 2 = 3
[ForkJoinPool-1-worker-3] - join() 4 + 5 = 9
[ForkJoinPool-1-worker-0] - join() 3
[ForkJoinPool-1-worker-1] - fork() {1,3} + {4,5} = ?
[ForkJoinPool-1-worker-2] - fork() {1,2} + {3,3} = ?
[ForkJoinPool-1-worker-2] - join() {1,2} + {3,3} = 6
[ForkJoinPool-1-worker-1] - join() {1,3} + {4,5} = 15
15
用图来表示
teger> {
int begin;
int end;
public AddTask3(int begin, int end) {
this.begin = begin;
this.end = end;
}
@Override
public String toString() {
return "{" + begin + "," + end + '}';
}
@Override
protected Integer compute() {
if (begin == end) {
log.debug("join() {}", begin);
return begin;
}
if (end - begin == 1) {
log.debug("join() {} + {} = {}", begin, end, end + begin);
return end + begin;
}
int mid = (end + begin) / 2;
AddTask3 t1 = new AddTask3(begin, mid);
t1.fork();
AddTask3 t2 = new AddTask3(mid + 1, end);
t2.fork();
log.debug("fork() {} + {} = ?", t1, t2);
int result = t1.join() + t2.join();
log.debug("join() {} + {} = {}", t1, t2, result);
return result;
}
}
然后提交给 ForkJoinPool 来执行
```java
public static void main(String[] args) {
ForkJoinPool pool = new ForkJoinPool(4);
System.out.println(pool.invoke(new AddTask3(1, 10)));
}
结果
[ForkJoinPool-1-worker-0] - join() 1 + 2 = 3
[ForkJoinPool-1-worker-3] - join() 4 + 5 = 9
[ForkJoinPool-1-worker-0] - join() 3
[ForkJoinPool-1-worker-1] - fork() {1,3} + {4,5} = ?
[ForkJoinPool-1-worker-2] - fork() {1,2} + {3,3} = ?
[ForkJoinPool-1-worker-2] - join() {1,2} + {3,3} = 6
[ForkJoinPool-1-worker-1] - join() {1,3} + {4,5} = 15
15
用图来表示