为什么需要线程池?
线程池能够对线程进行统一分配,调优和监控:
- 降低资源消耗(线程无限制地创建,然后使用完毕后销毁)
- 提高响应速度(无须创建线程)
- 提高线程的可管理性
Java是如何实现和管理线程池的?
从JDK 5开始,把工作单元与执行机制分离开来,工作单元包括Runnable和Callable,而执行机制有Executor框架提供。
并发队列
学习之前的了解并发队列
- 非阻塞队列
- 阻塞队列(线程数使用的是阻塞队列)
非阻塞队列
public static void main(String[] args) {
//非阻塞队列
//无边界队列,没有长度限制
ConcurrentLinkedQueue<String> clq = new ConcurrentLinkedQueue();
//入队
clq.add("html");
clq.add("java");
clq.add("mysql");
//出队 先进先出
System.out.println(clq.poll());//html ---出队并且在队列移除元素
System.out.println(clq.size());//2
System.out.println("----------------------");
System.out.println(clq.peek());//java ---出队并且在队列不移除元素
System.out.println(clq.size());//2
}
阻塞队列
public static void main(String[] args) throws InterruptedException {
//阻塞队列
//有边界队列 边界为3
// BlockingDeque为接口具体实现类自己查阅API
BlockingDeque<String> blockingDeque = new LinkedBlockingDeque<>(3);
blockingDeque.offer("HTML");
blockingDeque.offer("java");
blockingDeque.offer("css");
//3秒时间判断是否可以插入mySql
blockingDeque.offer("mySql",3, TimeUnit.SECONDS);
//移除4个
System.out.println(blockingDeque.poll());
System.out.println(blockingDeque.poll());
System.out.println(blockingDeque.poll());
System.out.println(blockingDeque.poll());//null ---此时边界为3并没有插入
}
打印结果 此时会等待3秒钟
HTML
java
css
null
当提前移除元素时
public static void main(String[] args) throws InterruptedException {
//阻塞队列
//有边界队列 边界为3
// BlockingDeque为接口具体实现类自己查阅API
BlockingDeque<String> blockingDeque = new LinkedBlockingDeque<>(3);
blockingDeque.offer("HTML");
blockingDeque.offer("java");
blockingDeque.offer("css");
System.out.println(blockingDeque.poll());
//3秒时间判断是否可以插入mySql
blockingDeque.offer("mySql",3, TimeUnit.SECONDS);
//移除4个
System.out.println(blockingDeque.poll());//HTML
System.out.println(blockingDeque.poll());//java
System.out.println(blockingDeque.poll());//css
System.out.println(blockingDeque.poll());//null ---此时边界为3并没有插入
}
打印结果 此时队列中有空间无需等待3秒
HTML
java
css
mySql
null
线程池底层原理
java.uitl.concurrent.ThreadPoolExecutor类是线程池中最核心的一个类
所有线程池用的都是阻塞队列
示例
public static void main(String[] args) {
// 核心线程数,最大线程数(超过会报错),创建出来的线程消亡时间,时间单位,阻塞队列(有界值为3)
ThreadPoolExecutor tpe = new ThreadPoolExecutor(1,2,3, TimeUnit.SECONDS,new LinkedBlockingDeque<>(3));
//创建一个线程
Thread thread = new Thread(() -> System.out.println(Thread.currentThread().getName()));
//添加一个线程
tpe.execute(thread);
tpe.execute(thread);
tpe.execute(thread);
tpe.execute(thread);
//关闭线程池
tpe.shutdown();
}
打印结果 此时使用的都是一个线程
pool-1-thread-1
pool-1-thread-1
pool-1-thread-1
pool-1-thread-1
由上例可知ThreadPoolExecutor对应构造方法的参数,当创建好线程池设置完参数,往线程池添加任务时当大于核心线程数时,此时剩下的任务会放在阻塞队列中等待任务完成后继续执行。
当超出队列时
public static void main(String[] args) {
// 核心线程数,最大线程数(超过会报错),创建出来的线程消亡时间,时间单位,阻塞队列(有界值为3)
ThreadPoolExecutor tpe = new ThreadPoolExecutor(1,2,3, TimeUnit.SECONDS,new LinkedBlockingDeque<>(3));
//创建一个线程
Thread thread = new Thread(() -> System.out.println(Thread.currentThread().getName()));
//添加一个线程
tpe.execute(thread);
tpe.execute(thread);
tpe.execute(thread);
tpe.execute(thread);
//超出队列
// tpe.execute(thread); //会出现pool-1-thread-2
//关闭线程池
tpe.shutdown();
}
打印结果
pool-1-thread-1
pool-1-thread-1
pool-1-thread-1
pool-1-thread-2
pool-1-thread-1
此时会出现线程pool-1-thread-2 原因是因为当队列无法装下多余任务的时候此时线程池会另外创建对应线程去处理没有存储在阻塞队列里的任务。
创建的线程不大于maximumPoolSize
当超过最大最大线程数maximumPoolSize时
public static void main(String[] args) {
// 核心线程数,最大线程数(超过会报错),创建出来的线程消亡时间,时间单位,阻塞队列(有界值为3)
ThreadPoolExecutor tpe = new ThreadPoolExecutor(1,2,3, TimeUnit.SECONDS,new LinkedBlockingDeque<>(3));
//创建一个线程
Thread thread = new Thread(() -> System.out.println(Thread.currentThread().getName()));
//添加一个线程
tpe.execute(thread);
tpe.execute(thread);
tpe.execute(thread);
tpe.execute(thread);
//超出队列
tpe.execute(thread); //会出现pool-1-thread-2
//超出最大线程数
tpe.execute(thread);//此时报错
//关闭线程池
tpe.shutdown();
}
打印结果
Exception in thread "main" java.util.concurrent.RejectedExecutionException: Task Thread[Thread-0,5,main] rejected from java.util.concurrent.ThreadPoolExecutor@68f7aae2[Running, pool size = 2, active threads = 2, queued tasks = 0, completed tasks = 3]
at java.util.concurrent.ThreadPoolExecutor$AbortPolicy.rejectedExecution(ThreadPoolExecutor.java:2063)
at java.util.concurrent.ThreadPoolExecutor.reject(ThreadPoolExecutor.java:830)
at java.util.concurrent.ThreadPoolExecutor.execute(ThreadPoolExecutor.java:1379)
at com.example.demo.com.thread.ThreadPoor.main(ThreadPoor.java:25)
pool-1-thread-1
pool-1-thread-2
pool-1-thread-1
pool-1-thread-2
pool-1-thread-1
此时就会抛出异常
线程池种类
public static void main(String[] args) {
//可缓存 最大线程数Integer.MAX_VALUE 最大值 当某些线程使用完 会拿来继续使用
ExecutorService cache = Executors.newCachedThreadPool();
// for(int i =1 ;i<100;i++){
// cache.execute(new Thread(()->System.out.println(Thread.currentThread().getName())));
// }
cache.shutdown();
//定长 最大线程数是核心线程数 核心线程数是多少就是多少
ExecutorService fixed = Executors.newFixedThreadPool(3);
for (int i = 1; i < 100; i++) {
// fixed.execute(new Thread(()->System.out.println(Thread.currentThread().getName())));
}
fixed.shutdown();
//定时 多长时间执行该任务
ScheduledExecutorService scheduled = Executors.newScheduledThreadPool(3);
for (int i = 1; i < 100; i++) {
// scheduled.schedule(new Thread(()->System.out.println(Thread.currentThread().getName())),3, TimeUnit.SECONDS);
}
scheduled.shutdown();
//单例线程 只有一个线程去执行
ExecutorService single = Executors.newSingleThreadExecutor();
for (int i = 1; i < 100; i++) {
single.execute(new Thread(() -> System.out.println(Thread.currentThread().getName())));
}
single.shutdown();
}
}
Callable创建线程和线程池创建Callable线程
继承Callable < Integer> 返回integer类型
public class CallableTest implements Callable<Integer>{
@Override
public Integer call() throws Exception {
//返回随机数
return new Random().nextInt(10);
}
public static void main(String[] args) {
CallableTest callableTest = new CallableTest();
//中介
FutureTask<Integer> futureTask = new FutureTask<>(callableTest);
Thread thread = new Thread(futureTask);
//本质启动的是futureTask 的run方法
thread.start();
Integer integer = null;
try {
integer = futureTask.get();
//一个停两秒
System.out.println(integer);
} catch (InterruptedException e) {
e.printStackTrace();
} catch (ExecutionException e) {
e.printStackTrace();
}
}
}
线程池创建线程
public static void main(String[] args) throws Exception {
ExecutorService es = Executors.newFixedThreadPool(3);
List<Future<String>> list = Lists.newArrayList();
//100个任务
for (int i = 1;i<100;i++){
Future<String> submit = es.submit(new Callable<String>() {
@Override
public String call() throws Exception {
Thread.sleep(2000);
return Thread.currentThread().getName();
}
});
list.add(submit);
}
list.forEach((x) -> {
try {
//此时三个一组停两秒 核心线程为三个每次在阻塞队列取出来的是三个 所有打印的是三个一组停两秒
System.out.println(x.get());
} catch (InterruptedException e) {
e.printStackTrace();
} catch (ExecutionException e) {
e.printStackTrace();
}
});
两者区别:
- 继承Callable:当线程执行run方法时每打印一次结果都会阻塞两秒
- 使用线程池:每打印(边界值)次结果 阻塞两秒 是因为每次获取结果时 线程池里线程每次拿到线程数的任务,执行完指定任务统一返回数据 此时阻塞队列获取时会阻塞两秒