线程池
什么是线程池
线程池是多线程的处理形式,在处理的过程中将任务添加到队列中,然后在创建线程后启动这些任务。线程池会维护这多条线程,等待监督管理者分配并发执行的任务。线程池不仅要能够保证内核的充分利用,还能防止过度调用。
Java内嵌线程池,
为什么要用线程池
- 当我们创建线程过多时,会容易引发内存溢出,因此我们有必要使用线程池。
- 可以根据系统的需求和硬件环境来灵活的控制线程的数量,并且可以对所有的线程进行统一的管理和控制,从而提高系统的运行效率,降低系统的运行压力。
使用线程池有哪些优点缺点
优点
- 线程和任务分离,提高线程的重用性。
- 控制线程的并发数量,统一管理所有的线程,从而降低服务器的压力。
- 在多个线程同时执行时,可以提高程序的运行效率。
- 避免大量的线程为了抢占资源而产生的阻塞效果。
缺点
- 占用了一定的内存空间。
- 线程越多,cpu的调度开销越大。
线程池应用哪些场景
企业场景
- 商品的秒杀
- 网盘的下载上传
- 12306网上购票系统
只要有并发的地方,任务数据或大或小,时间或长或短都可以使用线程池;
线程池的使用
/**
* corePoolSize 线程池中的线程数
* maximumPoolSize 线程池允许的最大线程数
* keepAliveTime 当线程数量大于核心时,这是剩余空闲线程在终止前等待新任务的最大时间
* unit 时间单位
* workQueue 在执行任务之前用来保存任务的队列
**/
public ThreadPoolExecutor(int corePoolSize,
int maximumPoolSize,
long keepAliveTime,
TimeUnit unit,
BlockingQueue<Runnable> workQueue) {
this(corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue,
Executors.defaultThreadFactory(), defaultHandler);
}
/**
* threadFactory 创建多线程时使用的工厂
**/
public ThreadPoolExecutor(int corePoolSize,
int maximumPoolSize,
long keepAliveTime,
TimeUnit unit,
BlockingQueue<Runnable> workQueue,
ThreadFactory threadFactory) {
this(corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue,
threadFactory, defaultHandler);
}
/**
* handler 当执行因线程边界和队列容量达到而被阻塞时使用的处理程序
**/
public ThreadPoolExecutor(int corePoolSize,
int maximumPoolSize,
long keepAliveTime,
TimeUnit unit,
BlockingQueue<Runnable> workQueue,
RejectedExecutionHandler handler) {
this(corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue,
Executors.defaultThreadFactory(), handler);
}
参数说明
核心线程数(corePoolSize)
- 核心线程数的设计需要依据任务的处理时间和每秒产生的任务数量来确定。
- 例如:执行一个任务需要0.1秒,系统的百分之80的时间每秒都会产生100个任务,那就要想在1秒内处理完这些100个任务,那就需要10个线程,此时我们就可以设计核心线程数为10,实际开发中不能这么平均,一般按照8020原则设计,依照80%的情况设计核心线程数,剩下的20%可以以用最大的线程数来处理。
最大线程数(maximumPoolSize)
- 最大线程数的设计除了需要参照核心数的条件外,还需要参照系统每秒产生的最大任务数来决定。
- 例如:如果系统每秒最大产生的任务是1000,那么,最大线程数=(最大任务数-任务对列长度)单个任务执行时间。=====最大线程数=(1000-200)0.1=80个
最大空闲时间(keepAliveTime)
- 这个参数的是设计完全参考系统执行环境和硬件压力设定的,没有固定的参考值,用户可以根据经验和系统产生任务的时间间隔合理设置一个值就行。
任务队列长度(workQueue)
- 任务队列长度一般设计为:核心线程数/单个任务执行时间*2即可,
- 例如:核心线程数设计为100,单个任务执行时间0.1秒,队列阐述为200.
时间单位(unit)
- 指定keepAliveTime参数的时间单位,常用的有单位:
- 毫秒(TimeUnit.MILLISECONDS)
- 秒(TimeUnit.SECONDS)
- 分(TimeUnit.MINUTES)
线程工厂(ThreadFactory)
- 用于指定为线程池创建新的线程的方式。
拒绝策略(handler)
- 当达到最大线程数量时需要执行的饱和策略
常见的线程池
- newSingleThreadExenctor
- 单个线程的线程池,即线程池种每一个只有一个线程工作,单线程执行任务.
- newFixedThreadExecutor
- 固定数量的线程池,每提交一个任务就是一个线程,直到达到线程池的最大数量,然后后面的进入等待队列知道前面的任务完成才继续执行.
- newCacheThreadExecutor(推荐使用)
- 可缓存线程池,当线程池大小超过了处理任务所需要的线程,那就会回收一部分空闲(一分钟无执行)的线程,当有任务来时,又能智能的添加新线程来执行.
- newScheduleThreadExecutor
- 没有大小限制的线程池,支持定时和周期性的执行线程.
线程的工作流程
自定义线程池
1,编写任务类(MyTask),实现Runnable接口,
package com.sin.demo;
import java.util.concurrent.ThreadPoolExecutor;
/**
* 任务类
* 需求:
* 自定义线程联系,要实现Runnable接口
* 包含任务编号,每个任务执行时间设计为0.2秒
*/
public class MyTask implements Runnable{
private int id;
public MyTask(int id) {
this.id = id;
}
@Override
public void run() {
String name =Thread.currentThread().getName();
System.out.println("线程:"+name+"即将执行任务:"+id);
try {
Thread.sleep(200);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("线程:"+name+"完成任务:"+id);
}
@Override
public String toString() {
return "MyTask{" +
"id=" + id +
'}';
}
}
2,编写线程类(MyWorker),用于执行任务,需要持有所有任务
package com.sin.demo;
import java.util.List;
/**
* 线程类,
* 需求:
* 需要继承Thread类,设计一个属性,用于保存线程的名字
* 设计一个集合,用于保存所有的任务。
*/
public class MyWorker extends Thread{
//用来保存线程的名字
private String name;
private List<Runnable> tashs;
//利用有参构造方法,给成员变量赋值
public MyWorker(String name,List<Runnable> tashs){
super(name);
this.tashs = tashs;
}
@Override
public void run() {
//判断集合中是否有任务,只要时有,那就之一执行下去
while(tashs.size()>0){
Runnable runnable = tashs.remove(0);
runnable.run();
}
}
}
3,编写线程池类(MyThreadPool),包括提交任务,执行任务的能力,
package com.sin.demo;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.List;
/**
* 线程池类
* 成员变量:
* 1,任务队列, 集合, 控制线程安全问题
* 2,当前线程数量
* 3,核心线程数量
* 4,最大线程数量
* 5,任务队列的长度
* 成员方法:
* 1,提交任务:
* 将任务添加到集合中,需要判断是否超出了任务的总长度
* 2,执行任务:
* 判断当前线程的数量,决定创建核心线程还是非核心线程
*/
public class MyThreadPool {
//成员变量:
//1,任务队列
private List<Runnable> tasks = Collections.synchronizedList(new ArrayList());
//2,当前线程数量
private int num;
//3,核心线程数量
private int corePoolSize;
//4,最大线程数量
private int maxSize;
//5,任务队列的长度
private int workSize;
public MyThreadPool( int corePoolSize, int maxSize, int workSize) {
this.corePoolSize = corePoolSize;
this.maxSize = maxSize;
this.workSize = workSize;
}
//成员方法:
//1,提交任务
public void submit(Runnable runnable){
//判断当前集合中任务的数量,是否超出了最大任务数
if (tasks.size()>=workSize){
System.out.println("该任务:"+runnable+"被丢弃掉了");
}else {
tasks.add(runnable);
//执行任务
execTask(runnable);
}
}
//2,执行任务
public void execTask(Runnable runnable){
//判断当前线程池的线程总数量,是否超出了核心数
if (num<corePoolSize){
new MyWorker("核心线程:"+num,tasks).start();
num++;
}else if(num<maxSize){
new MyWorker("非核心线程:"+num,tasks).start();
num++;
}else {
System.out.println("任务:"+runnable+"被缓存了");
}
}
}
4,编写测试类(MyTest),创建线程对象,提交多个任务测试。
package com.sin.demo;
/**
* 测试类:
* 1,创建线程池类对象;
* 2,提交多个任务;
*/
public class MyTest {
public static void main(String[] args) {
//1,创建线程池类对象(corepoolSize:核心线程数量,mixSize:最大线程数,workSize:任务队列的长度)
MyThreadPool pool = new MyThreadPool(2,4,20);
//2,提交多个任务
for (int i=0;i<30;i++){
//创建任务对象,并提交给线程池
MyTask myTask = new MyTask(i);
pool.submit(myTask);
}
}
}
内置线程池
ExecutorService
- 是Java内置的线程接口。
- void shutdown():
- 启动前提交的有序关闭,任务被执行,但是不接受新的任务。
- List shutdownNow():
- 停止所有正在执行的任务,暂停处理正在等待的任务,并返回等待执行的任务列表。
- Future submit(Callable task):
- 执行带返回值的任务,返回一个Future对象。
- Future submit(Runnable task, T result):
- 执行Runnable任务,并且返回标识该任务的Future.
- Future<?> submit(Runnable task):
- 执行Runnable任务,并且返回一个表示该任务的Future。
- void shutdown():
ExecutorService获取
获取ExecutorService可以利用jdk中的Executors类中的静态方法
- static ExecutorService newFixedThreadPool(int nThreads):
- 创建一个可重用的固定线程数的线程池
- static ExecutorService newWorkStealingPool(int parallelism):
- 创建一个线程池,以维护足够的线程来支持。
- static ExecutorService newFixedThreadPool(int nThreads, ThreadFactory threadFactory):
- 创建一个可重用固定线程出的线程池,线程池中的所有线程都是用线程工厂ThreadFactory创建。
- static ExecutorService newSingleThreadExecutor():
- 创建一个使用单个worker线程的Executor,以无界队列方式类运行该线程。
- static ExecutorService newSingleThreadExecutor(ThreadFactory threadFactory):
- 创建一个使用单个worker线程的Executor,且线程池中的所有线程都使用线程过长threadFactory来创建。
newCachedThreadPool
package com.sin.demo;
/**
* 任务类,包含一个任务编号,在任务中,打印出是哪一个线程正在执行任务
*/
public class MyRunnable implements Runnable {
private int id;
public MyRunnable(int id) {
this.id = id;
}
@Override
public void run() {
//获取线程的名称,
String name =Thread.currentThread().getName();
System.out.println("ThreadName--:"+name+"runningID--:"+id);
}
}
package com.sin.demo;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.ThreadFactory;
/**
* 练习Executor获取ExecutorService,然后调用方法,提交任务。
*/
public class MyTest02 {
public static void main(String[] args) {
//test1();
test2();
}
private static void test1(){
//使用工厂类获取线程对象
ExecutorService executorService = Executors.newCachedThreadPool();
//提交任务
for(int i=1;i<=10;i++){
executorService.submit(new MyRunnable(i));
}
}
private static void test2(){
//使用工厂类获取线程池对象
ExecutorService executorService = Executors.newCachedThreadPool(new ThreadFactory() {
int n = 1;
@Override
public Thread newThread(Runnable r) {
return new Thread(r,"SetThreadName:"+n++);
}
});
//提交任务
for (int i=1;i<=10;i++){
executorService.submit(new MyRunnable(i));
}
}
}
newFixeThreadPool
package com.sin.demo;
/**
* 任务类,包含了一个任务编号,在任务中,打印出一是那个一个线程正在执行任务
*/
public class MyRunnable2 implements Runnable{
private int id;
public MyRunnable2(int id) {
this.id = id;
}
@Override
public void run() {
//获取线程的名称,打印出来
String name = Thread.currentThread().getName();
System.out.println("ThreadName----->:"+name+"runningId----->:"+id);
}
}
package com.sin.demo;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.ThreadFactory;
/**
* 测试类
*/
public class MyTest03 {
public static void main(String[] args) {
//test1();
test2();
}
private static void test1(){
//使用工厂类获取线程对象
ExecutorService executorService = Executors.newFixedThreadPool(3);
//提交任务
for (int i=1;i<=10;i++){
executorService.submit(new MyRunnable2(i));
}
}
private static void test2(){
//使用工厂类获取线程池对象
ExecutorService executorService = Executors.newFixedThreadPool(3, new ThreadFactory() {
int n = 1;
@Override
public Thread newThread(Runnable r) {
return new Thread(r,"setThreadName---->"+n++);
}
});
//提交任务
for (int i=1;i<=10;i++){
executorService.submit(new MyRunnable2(i));
}
}
}
综合案例-秒杀
案例介绍:
我看某个商场退出了秒杀活动,上10部爱疯13免费送给参与直播间里的客户,
要求:
1,使用线程池来创建线程
2,解决线程安全问题。
思路:
1,既然商品数量为10个,那么我们就可以创建线程池的时候将初始显示书来给你为10或者以下,设计线程池最大数量10;
2,当某个线程执行任务只好,可以让其他秒杀的人继续是用该线程参与秒杀;
3,使用synchronized控制线程安全,防止出现错误数据。
代码流程:
1,编写任务类,主要是送出手机给秒杀成功的客户;
2,编写主程序类,创建20个任务(20个客户参与);
3,创建线程池对象并且接收出这20个,开始执行20.
任务类
package com.sin.demo;
/**
* 任务类:
* 包含了商品数量,客户名称,送出爱疯的行为。
*/
public class MyTask implements Runnable{
//设计一个变量,爱疯13的数量
private static int id=10;
//设计一个客户名称
private String userName;
public MyTask(String userName) {
this.userName = userName;
}
@Override
public void run() {
String name = Thread.currentThread().getName();
System.out.println(userName+"user:ing"+name+"miaosha:ing");
try{
Thread.sleep(200);
}catch (Exception e){
e.printStackTrace();
}
synchronized (MyTask.class){
if(id>0){
System.out.println(userName+"use--->"+name+"miaosha---->"+id--+"id,success");
}else {
System.out.println(userName+"use--->"+name+"shibei");
}
}
}
}
主线程类
package com.sin.demo;
import java.util.concurrent.LinkedBlockingDeque;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
/**
* 主线程列,测试任务类
*/
public class MyTest05 {
public static void main(String[] args) {
//1,创建一个线程池对象
ThreadPoolExecutor executor = new ThreadPoolExecutor(3,5,1, TimeUnit.MINUTES,
new LinkedBlockingDeque<>(15));
//2,循环创建任务对象
for(int i=1;i<=20;i++){
MyTask myTask = new MyTask("userName"+i);
executor.submit(myTask);
}
//3,关闭线程池
executor.shutdown();
}
}