JUC-线程池
一,问题
在没有使用线程池的时候,每次需要一个线程都得手动new Thread()方式创建线程,用完了再销毁。
我们使用线程的时候就去创建一个线程,这样实现起来非常简便,但是就会有一个问题:
如果并发的线程数量很多,并且每个线程都是执行一个时间很短的任务就结束了,这样频繁创建线程就会大大降低系统的效率,因为频繁创建线程和销毁线程需要时间。
那么有没有一种办法使得线程可以复用,就是执行完一个任务,并不被销毁,而是可以继续执行其他的任务?
在Java中可以通过线程池来达到这样的效果。
Executor线程池框架,通过它把任务的提交和执行进行解耦,只需要定义好任务,然后提交给线程池,而不用关心该任务是如何执行、被哪个线程执行,以及什么时候执行。
一、线程池:提供了一个线程队列,队列中保存着所有等待状态的线程。避免了创建与销毁额外开销,提高了响应的速度。
二、线程池的体系结构:
java.util.concurrent.Executor : 负责线程的使用与调度的根接口
|--**ExecutorService 子接口: 线程池的主要接口
|--ThreadPoolExecutor 线程池的实现类
|--ScheduledExecutorService 子接口:负责线程的调度
|--ScheduledThreadPoolExecutor :继承 ThreadPoolExecutor, 实现 ScheduledExecutorService(即实现了接口,又继承了ThreadPoolExecutor实现类)
三、工具类 : Executors
ExecutorService newFixedThreadPool() : 创建固定大小的线程池
ExecutorService newCachedThreadPool() : 缓存线程池,线程池的数量不固定,可以根据需求自动的更改数量。
ExecutorService newSingleThreadExecutor() : 创建单个线程池。线程池中只有一个线程 ScheduledExecutorService newScheduledThreadPool() : 创建固定大小的线程,可以延迟或定时的执行任务(调度)。
四:操作步骤:以newFixedThreadPool为例:
1,创建一个5个线程的线程池:
ExecutorService pool = Executors.newFixedThreadPool(5);
2,创建任务对象,任务对象可以是Runnable接口对象,也可以是Callable接口对象,这里以Runnable接口对象为例:
类定义:
class ThreadPoolDemo implements Runnable { public void run(){ do something}}
创建任务:
ThreadPoolDemo tpd = new ThreadPoolDemo();
2,向线程池中加任务:使用pool.submit(任务对象)添加任务。
线程池添加任务有好几种方式:
submit(Callable<T> task)
submit(Runnable task, T result)
submit(Runnable task)
以最后一直为例:
pool.submit(tpd);
3,线程执行完,关闭线程:
线程关闭有两种方法:
第一种:shutdown(),这种方式会等线程池中线程执行完了就关闭线程池。
第二张:shutdownNow(),不管线程池里面任务有没有执行完全,都会立刻关闭线程池。
五,Submit:一共有三种构造方法:
1,参数是Callable对象,可以获取任务的返回结果。
<T> Future<T> submit(Callable<T> task);
2,参数是Runnable对象,有返回结果
<T> Future<T> submit(Runnable task, T result);
3,参数也是Runnable对象
Future<?> submit(Runnable task);
5.1 分析 submit(Runnable task, T result)
之前的章节知道,Callable是任务是可以有返回值的,而Runnable是没有返回值的。但是对于第二个构造方法,使用Runnable对象作为参数也能返回结果。
那么结果是如何返回的呢,又是返回什么结果呢?我跟了一下源码,源码关键代码如下:
有一个实现类AbstractExecutorService,里面有一个方法:
public <T> Future<T> submit(Runnable task, T result) {
if (task == null) throw new NullPointerException();
RunnableFuture<T> ftask = newTaskFor(task, result);
execute(ftask);
return ftask;
}
跟踪:newTaskFor(task, result);
通过这个方法一直跟踪,发现最后调用的是Excutors里面一个方法:
public static <T> Callable<T> callable(Runnable task, T result) {
if (task == null)
throw new NullPointerException();
return new RunnableAdapter<T>(task, result);
}
可知最后在返回的结果是下面这个类返回的
new RunnableAdapter<T>(task, result)
类RunnableAdapter很简单:
static final class RunnableAdapter<T> implements Callable<T> {
final Runnable task;
final T result;
RunnableAdapter(Runnable task, T result) {
this.task = task;
this.result = result;
}
public T call() {
task.run();
return result;
}
}
通过这个类,我们可以大胆猜想到返回的结果是什么:
通过submit(Runnable task, T result)分别传入两个参数,一个是task,一个是result。
线程运行的时候会调用CALL方法,里面先通过task.run调用Runnable对象里面自定义的run方法,然后直接返回result。
可见整个线程执行,没有对result进行其他操作,只是传入对象引用,最后返回。
可见:唯一可以修改result值的地方,就是通过Ruannabe对象task的run方法里面执行,线程任务执行完了就返回值。
也就是result传入是哪个对象,最终Future对象的get()获取到的就是哪个对象,只不过值的改变是在Runnbale里面的run方法里面修改的。
看下面这个例子感受一下:
import java.util.concurrent.ExecutionException;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Future; public class TestSubmit { public static void main(String[] args) throws InterruptedException, ExecutionException { ExecutorService executor = Executors.newSingleThreadExecutor();
Data data = new Data();
//通过Future对象接收执行结果
Future<Data> future = executor.submit(new Task(data), data);
System.out.println(future.get().getName());
}
}
class Data {
String name; public String getName() {
return name;
} public void setName(String name) {
this.name = name;
}
} class Task implements Runnable {
Data data; public Task(Data data) {
this.data = data;
}
public void run() {
System.out.println("This is ThreadPoolExetor#submit(Runnable task, T result) method.");
data.setName("kevin");
}
}
执行结果:
This is ThreadPoolExetor#submit(Runnable task, T result) method.
kevin
分析执行过程:
先定义了一个Data实例data,然后分别把引用传给Task类里面的构造方法,和result参数。
线程执行的过程中,修改了data的值,线程执行完了,返回result的值,也就是data的值。通过future.get()获取该data修改后的值。
5.2 操作submit(Runnable task);
例子如下:
package com.atguigu.juc; import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Future; public class TestThreadPool
{
public static void main(String[] args) throws Exception {
//1. 创建线程池
ExecutorService pool = Executors.newFixedThreadPool(5); ThreadPoolDemo tpd = new ThreadPoolDemo();
//2. 为线程池中的线程分配任务
for (int i = 0; i < 10; i++)
{
pool.submit(tpd);
}
//3. 关闭线程池
pool.shutdown();
}
}
class ThreadPoolDemo implements Runnable{ private int i = 0; @Override
public void run() {
while(i <= 100){
System.out.println(Thread.currentThread().getName() + " : " + i++);
}
}
}
5.3 使用:submit(Callable<T> task)
因为使用Callable有返回结果,可以通过Future对象获取。
先以单个线程为例:
package com.atguigu.juc; import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Future; public class TestThreadPool
{
public static void main(String[] args) throws Exception {
//1. 创建线程池
ExecutorService pool = Executors.newFixedThreadPool(5); Future<Integer> future = pool.submit(new Callable<Integer>(){ @Override
public Integer call() throws Exception {
int sum = 0; for (int i = 0; i <= 100; i++) {
sum += i;
}
return sum;
}
});
System.out.println(future.get()); pool.shutdown();
}
}
如果多线程呢:为了接收多线程的结果,使用一个List接收结果:
package com.atguigu.juc; import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Future; public class TestThreadPool
{
public static void main(String[] args) throws Exception {
//1. 创建线程池
ExecutorService pool = Executors.newFixedThreadPool(5);
//创建一个List对象接收结果
List<Future<Integer>> list = new ArrayList<>(); for (int i = 0; i < 10; i++) {
Future<Integer> future = pool.submit(new Callable<Integer>(){ @Override
public Integer call() throws Exception {
int sum = 0; for (int i = 0; i <= 100; i++) {
sum += i;
}
return sum;
}
});
//向list里面添加结果
list.add(future);
}
//关闭线程
pool.shutdown(); for (Future<Integer> future : list) {
System.out.println(future.get());
}
}
}
JUC-线程池的更多相关文章
- 【JUC】JUC线程池框架综述
一.前言 在分析完了JUC的锁和集合框架后,下面进入JUC线程池框架的分析,下面给出JUC线程池的总体框架,之后再逐一进行分析. 二.JUC线程池框架图 说明:从上图可知,JUC线程池框架中的其他接口 ...
- java多线程系类:JUC线程池:03之线程池原理(二)(转)
概要 在前面一章"Java多线程系列--"JUC线程池"02之 线程池原理(一)"中介绍了线程池的数据结构,本章会通过分析线程池的源码,对线程池进行说明.内容包 ...
- java多线程系类:JUC线程池:02之线程池原理(一)
在上一章"Java多线程系列--"JUC线程池"01之 线程池架构"中,我们了解了线程池的架构.线程池的实现类是ThreadPoolExecutor类.本章,我 ...
- Java多线程系列--“JUC线程池”06之 Callable和Future
概要 本章介绍线程池中的Callable和Future.Callable 和 Future 简介示例和源码分析(基于JDK1.7.0_40) 转载请注明出处:http://www.cnblogs.co ...
- Java多线程系列--“JUC线程池”02之 线程池原理(一)
概要 在上一章"Java多线程系列--“JUC线程池”01之 线程池架构"中,我们了解了线程池的架构.线程池的实现类是ThreadPoolExecutor类.本章,我们通过分析Th ...
- Java多线程系列--“JUC线程池”03之 线程池原理(二)
概要 在前面一章"Java多线程系列--“JUC线程池”02之 线程池原理(一)"中介绍了线程池的数据结构,本章会通过分析线程池的源码,对线程池进行说明.内容包括:线程池示例参考代 ...
- Java多线程系列--“JUC线程池”04之 线程池原理(三)
转载请注明出处:http://www.cnblogs.com/skywang12345/p/3509960.html 本章介绍线程池的生命周期.在"Java多线程系列--“基础篇”01之 基 ...
- Java多线程系列--“JUC线程池”05之 线程池原理(四)
概要 本章介绍线程池的拒绝策略.内容包括:拒绝策略介绍拒绝策略对比和示例 转载请注明出处:http://www.cnblogs.com/skywang12345/p/3512947.html 拒绝策略 ...
- Java - "JUC线程池" 架构
Java多线程系列--“JUC线程池”01之 线程池架构 概要 前面分别介绍了"Java多线程基础"."JUC原子类"和"JUC锁".本章介 ...
- Java - "JUC线程池" ThreadPoolExecutor原理解析
Java多线程系列--“JUC线程池”02之 线程池原理(一) ThreadPoolExecutor简介 ThreadPoolExecutor是线程池类.对于线程池,可以通俗的将它理解为"存 ...
随机推荐
- Redis 性能问题的记录
最近线上使用redis, 查询的情况不甚理想, 这个查询操作是个 lua 脚本, 包含如下操作 开发机 redis, 没有其他干扰, 插入的 zset 有 5000 member 左右, 使用的 re ...
- Python traceback 模块, 打印异常信息
Python感觉是模仿Java, 到处都需要加try..catch.... 这里记录一下用法,方便后续使用. # -*- coding:utf-8 -*- import os import loggi ...
- go语言之进阶篇主协程先退出
1.主协程先退出 示例: package main import ( "fmt" "time" ) //主协程退出了,其它子协程也要跟着退出 func main ...
- 如何移除EFI system partition?
莫名其妙, 在我的服务器上出现了这样一种分区, 上面写着EFI system, 删也删不掉, 因为删除分区的菜单是灰掉的. 找到了这篇文章, 成功的删掉了这个烦人的分区. 整个过程记录如下: 参考 ...
- Sequence在Oracle中的使用
Oracle中,当需要建立一个自增字段时,需要用到sequence.sequence也可以在mysql中使用,但是有些差别,日后再补充,先把oracle中sequence的基本使用总结一下,方便日后查 ...
- oracle的行级触发器使用
行级触发器: 当触发器被触发时,要使用被插入.更新或删除的记录中的列值,有时要使用操作前.后列的值. :NEW 修饰符访问操作完成后列的值 :OLD 修饰符访问操作完成前列的值 例1: 建立一个触发器 ...
- C指针原理(14)
tcc源码分析 本博客所有内容是原创,如果转载请注明来源 http://blog.csdn.net/myhaspl/ tcctok.h定义了C语言的词法分析的基本元素,主要定义了关键字. /* key ...
- AAAI 2018 论文 | 蚂蚁金服公开最新基于笔画的中文词向量算法
AAAI 2018 论文 | 蚂蚁金服公开最新基于笔画的中文词向量算法 2018-01-18 16:13蚂蚁金服/雾霾/人工智能 导读:词向量算法是自然语言处理领域的基础算法,在序列标注.问答系统和机 ...
- scikit-learn——快速入门 - daniel-D(转)
ML sklearn快速入门 申明:该系列博客是学习 sklearn 的笔记,内容将涵盖大部分机器学习的方法.本人微博@迅猛龙Daniel,能力有限,存在任何问题,希望共同交流.该博客采用马克飞象专业 ...
- Logistic 回归梯度上升优化函数
In [183]: def loadDataSet(): dataMat = [] labelMat = [] fr = open('testSet.txt') for line ...