这部分我将介绍线程池的两个接口(普通调度池,定时调度池)和两个类(线程池核心类,线程池工具类)。
线程池优点:
- 线程池里有很多已经创建好的线程,
提高线程的可管理性
- 线程已经创建好,当任务到达的时候可以直接执行,
执行任务速度较快
,任务到达可以很快处理 - 线程的
可重复利用率比较高
线程池的工作流程(四大核心组件)
1)核心池
2)阻塞队列
3)最大线程池
4)拒绝策略
工作流程:
1、若核心池未满,无论是否有空闲线程,都创建新线程执行任务,而后将其加入核心池中。若核心池已满,且没有空闲线程,执行步骤2,否则挑一个空闲线程执行任务。
2、将任务置入阻塞队列,若阻塞队列已满,执行步骤3。
3、若最大线程池未满,创建新线程执行任务而后加入最大线程池,若最大线程池已满,执行步骤4。
4、调用相应拒绝策略,处理任务。(默认拒绝策略:AbortPolicy- - -抛出异常,明确告诉调用方无法处理)
内置四大线程池
- 单线程池:任务需要顺序执行的场合(如A->B->C)
- 固定大小线程池:适用于负载较重的服务器或执行长期任务
- 缓存线程池:适用于负载较轻的服务器或短期的异步小任务
风险:若执行速度远小于提交速度,缓存池会不断创建新线程,有可能会将内存写满。 - 定时调度池:执行定时任务
缓存线程池简单应用:
1)提交速度>执行速度
class CachedTask implements Runnable {
@Override
public void run() {
System.out.println(Thread.currentThread().getName());
try {
TimeUnit.MILLISECONDS.sleep(200);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
public class ExecutorTest {
public static void main(String[] args) {
ExecutorService executorService = Executors.newCachedThreadPool();
for (int i = 0; i < 5; i++) {
executorService.submit(new CachedTask());
}
executorService.shutdown();
}
}
结果表明,当提交速度远大于执行速度时,需要不断创建新的线程来和新任务匹配。
2)提交速度<执行速度
class CachedTask implements Runnable {
@Override
public void run() {
System.out.println(Thread.currentThread().getName());
}
}
public class ExecutorTest {
public static void main(String[] args) {
ExecutorService executorService = Executors.newCachedThreadPool();
for (int i = 0; i < 5; i++) {
try {
TimeUnit.MILLISECONDS.sleep(200);
} catch (InterruptedException e) {
e.printStackTrace();
}
executorService.submit(new CachedTask());
}
executorService.shutdown();
}
}
结果表明,当提交速度小于执行速度时,当前任务处理完了,线程已经空闲,但是新的任务还没有来,所以不会创建新的线程。
1、普通调度池 ExcutorService
在这个子接口中有两个比较重要的方法:
void execute(Runnable command);
<T> Future<T> submit(Callable<T>||Runnable<T> task);
Future < T > 提供get()方法接收Callable的返回值
Future接口的get()属于阻塞操作,会将当前线程阻塞直到拿到Callable的返回值。
FutureTask类:保证任务在多线程场景下只会被执行一次。
class CallableDemo implements Callable<String> {
private int tickt = 20;
@Override
public String call() throws Exception {
for (int i = 0; i < 20; i++) {
if (tickt > 0) {
System.out.println(Thread.currentThread().getName() + "还剩下" + tickt-- + "票");
}
}
return "票已卖完,客官下次见";
}
}
public class FutureTaskDemo {
public static void main(String[] args) {
Callable<String> callable = new CallableDemo();
//Thread没法直接接收callable,需要借助futuretask
FutureTask futureTask = new FutureTask(callable);
Thread thread = new Thread(futureTask, "黄牛A");
Thread thread1 = new Thread(futureTask, "黄牛B");
Thread thread2 = new Thread(futureTask, "黄牛C");
thread.start();
thread1.start();
thread2.start();
}
}
结果显示只有黄牛A在卖票,原因就是FutureTask传入了一个callable对象,它会保证不管启动多少个线程,只要这个任务有人做了,其他人就不再做,这就是FutureTask的独有属性。
如何看到callable并行的执行这些任务呢,此时就需要借助线程池。
class CallableDemo implements Callable<String> {
private int ticket = 20;
@Override
public String call() throws Exception {
for (int i = 0; i < 20; i++) {
if (ticket > 0) {
System.out.println(Thread.currentThread().getName() +
"还剩下" + ticket-- + "票");
}
}
return "票已卖完,下次见";
}
}
public class FutureTaskDemo {
public static void main(String[] args) {
Callable<String> callable = new CallableDemo();
ExecutorService executorService = Executors.newFixedThreadPool(3);
for (int i = 0; i < 3; i++) {
executorService.submit(callable);
}
executorService.shutdown();
}
}
如果调用Future接口的get()方法会发生什么?
分两种情况,如果每提交一次任务就调用一次get()方法(代码中位置1),结果显示只有一个线程执行任务,其它两个线程直接打印“票已卖完”,因为当线程1执行完任务后,调用get()发生阻塞,当线程2,3创建完发现任务已经执行完了,所以直接打印“票已卖完”。
而如果在三个线程都提交之后再调用get()方法(代码中位置2),对之前的结果并不会有任何影响,因为在阻塞之前三个线程已经创建完,所以还是三个线程并行执行任务。
代码如下,可以试着执行一下:
class CallableDemo implements Callable<String> {
private int ticket = 20;
@Override
public String call() throws Exception {
for (int i = 0; i < 20; i++) {
if (ticket > 0) {
System.out.println(Thread.currentThread().getName() +
"还剩下" + ticket-- + "票");
}
}
return "票已卖完,下次见";
}
}
public class FutureTaskDemo {
public static void main(String[] args) throws ExecutionException, InterruptedException {
Callable<String> callable = new CallableDemo();
ExecutorService executorService = Executors.newFixedThreadPool(3);
Future<String> submit = null;
for (int i = 0; i < 3; i++) {
submit = executorService.submit(callable);
//System.out.println(submit.get()); ---位置1
}
System.out.println(submit.get()); // ---位置2
executorService.shutdown();
}
}
2、定时调度池 ScheduledExecutorService
执行定时任务或延迟任务
定时任务:闹钟,微信运动
延迟任务:计时器
3、线程池核心类 ThreadPoolExecutor
如何自定义线程池? -> 考的是自定义ThreadPoolExecutor的参数
public ThreadPoolExecutor(int corePoolSize, --- 核心池大小
int maximumPoolSize, --- 最大线程池大小
long keepAliveTime, --- 最大空闲时间
TimeUnit unit, --- 传入时间单位(s,ms...)
BlockingQueue<Runnable> workQueue, --- 阻塞队列
RejectedExecutionHandler handler) --- 拒绝策略
四个拒绝策略:
1)抛出异常
2)不处理不抛异常
3)替换队列:把一个阻塞队列最后的扔了,用我的
4)一直等待
阻塞队列:workQueue(***)
以下几个阻塞队列都在juc包下(并发编程包):
- ArrayBlockingQueue:基于数组的有界阻塞队列
- LinkedBlockingQueue:基于链表的无界阻塞队列
应用:固定大小、单线程池
- SynchrousQueue:一个不存储元素的无界阻塞队列,元素的入队与出队操作必须成对出现。
应用:缓存线程池采用此队列
4、线程池工具类 Executors
juc包下四大常用工具类:
1、闭锁CountDownLatch
几个比较重要的方法:
public CountDownLatch(int count):
需要等待的线程个数public void countDown():
等待的计数器减一public void await() throws InterruptedException:
调用await方法的线程会阻塞直到计数器值减为0
CountDownLatch的对象当计数器值减为0时,不可恢复。
class CDLTask implements Runnable {
//运动员线程
private CountDownLatch countDownLatch;
public CDLTask(CountDownLatch countDownLatch) {
this.countDownLatch = countDownLatch;
}
@Override
public void run() {
try {
TimeUnit.SECONDS.sleep(2);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + "到达终点");
countDownLatch.countDown();
}
}
public class CountDownLatchDemo {
// 裁判线程
public static void main(String[] args) {
//多线程计数器
CountDownLatch countDownLatch = new CountDownLatch(3);
CDLTask task = new CDLTask(countDownLatch);
Thread thread1 = new Thread(task, "运动员A");
Thread thread2 = new Thread(task, "运动员B");
Thread thread3 = new Thread(task, "运动员C");
System.out.println("开始比赛");
thread1.start();
thread2.start();
thread3.start();
// ...等待所有运动员到达终点
try {
//谁调用谁阻塞,直到countDownLatch的值减为0
countDownLatch.await();
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("比赛结束..");
}
}
2、循环栅栏CyclicBarrier
适用于若干个线程等待所有线程都到达一个状态之后再同时恢复执行,类比生活中交实验报告,开会。
public CyclicBarrier(int parties, Runnable barrierAction)
parties:
同时等待的线程个数
barrierAction:
恢复执行之前,随机挑选一个线程执行任务
CyclicBarrier值可以恢复。
简单应用:
class CBTask implements Runnable {
private CyclicBarrier cyclicBarrier;
public CBTask(CyclicBarrier cyclicBarrier) {
this.cyclicBarrier = cyclicBarrier;
}
@Override
public void run() {
System.out.println(Thread.currentThread().getName()
+ "已经到达会场");
try {
// 当前线程阻塞,直到所有线程都到达此位置
TimeUnit.SECONDS.sleep(2);
cyclicBarrier.await();
} catch (InterruptedException e) {
e.printStackTrace();
} catch (BrokenBarrierException e) {
e.printStackTrace();
}
System.out.println("所有人均已到场,会议开始");
}
}
public class CyclicBarrierDemo {
public static void main(String[] args) {
CyclicBarrier cyclicBarrier = new CyclicBarrier(3,
() -> {
System.out.println("当前线程为" +
Thread.currentThread().getName());
});
CBTask task = new CBTask(cyclicBarrier);
Thread thread1 = new Thread(task, "小王");
Thread thread2 = new Thread(task, "小刘");
Thread thread3 = new Thread(task, "小张");
thread1.start();
thread2.start();
thread3.start();
}
}
3、线程交换器Exchanger
两两线程配对后交换数据再同时恢复执行。
若线程调用Exchanger.exchange()
,缓冲区只有一个线程,则当前线程阻塞,直到有另一个线程调用Exchanger.exchange()。
简单应用:
public class ExchangerTest {
public static void main(String[] args) {
Exchanger<String> exchanger = new Exchanger<>();
Thread girlThread = new Thread(() -> {
try {
String str = exchanger.exchange("我喜欢你...");
System.out.println("女生说:" + str);
} catch (InterruptedException e) {
e.printStackTrace();
}
});
Thread boyThread = new Thread(() -> {
System.out.println("女神缓缓映入眼帘...");
try {
TimeUnit.SECONDS.sleep(1);
String str = exchanger.exchange("跟我好吧!");
System.out.println("男生说:" + str);
} catch (InterruptedException e) {
e.printStackTrace();
}
});
girlThread.start();
boyThread.start();
}
}
4、信号量Semaphore
Semaphore可以控制同时访问的线程个数,通过acquire()
获取一个许可,如果这个信号量被别人占有就等待,而release()
是释放一个许可。
public Semaphore(int permits)
permits表示许可个数(同时控制的资源个数)
简单应用:
//工人的工作任务
class SemaphoreTask implements Runnable {
private Semaphore semaphore;
public SemaphoreTask(Semaphore semaphore) {
this.semaphore = semaphore;
}
@Override
public void run() {
//获取设备
try {
semaphore.acquire();
System.out.println(Thread.currentThread().getName() + "占用一台设备生产。。。");
TimeUnit.SECONDS.sleep(2);
System.out.println(Thread.currentThread().getName() + "生产完毕,释放设备");
semaphore.release();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
public class SemaphoreTest {
public static void main(String[] args) {
//同时有5个设备可以生产
Semaphore semaphore = new Semaphore(5);
SemaphoreTask task = new SemaphoreTask(semaphore);
for (int i = 0; i < 8; i++) {
new Thread(task, "工人" + (i + 1)).start();
}
}
}
如果以后我们需要一个类来控制一组线程同时获取或者同时释放,就使用Semaphore 信号量。