线程池详解

一、线程存在的问题

1.创建线程需要开辟本地线程栈、虚拟机栈、程序计数器等私有线程内存,消耗的时候也需要释放这
些内存,频繁的创建和销毁需要⼀定的开销;
2.当任务数远远⼤于线程可以承载的数量之后,不能友好的进⾏任务拒绝。

二、线程池是什么

线程池(ThreadPool)是⼀种使用池化技术管理和使⽤线程的机制。
它是将多个线程预先存储在⼀个“池子”内,当有任务出现时可以避免重新创建和销毁线程所带来性能开销,只需要从“池子”内取出相应的线程执行对应的任务即可。

池化思想的应用:
a. 连接池(Connection Pooling):预先申请数据库连接,提升申请连接的速度,降低系统的开销。
b. 实例池(Object Pooling):循环使⽤对象,减少资源在初始化和释放时的昂贵损耗。

三、线程池的优点

线程池的好处是减少在创建和销毁线程上所消耗的时间以及系统资源的开销,解决资源不⾜的问题。
如果不使⽤线程池,有可能造成系统创建⼤量同类线程⽽导致消耗完内存或者“过度切换”的问题。

具体如下:
a.使用线程,从而避免线程重复创建和销毁的性能开销。
b. 控制线程数量,从而避免了因为线程创建过多而导致的内存溢出问题。
c.提供了任务管理功能,从而可以实现任务缓存和任务拒绝的情况。
d.提供更多更强⼤的功能:线程池具备可拓展性,允许开发⼈员向其中增加更多的功能。比如延时定时线程池ScheduledThreadPoolExecutor,就允许任务延期执⾏或定期执⾏。

四、线程池的使用

线程池的创建方法共有7种,但可以分为两类:
a. 通过 ThreadPoolExecutor 手动 创建的线程池(1种);
b. 通过 Executors 自动创建的线程池((6种)。

1.Executors.newFixedThreadPool:创建⼀个固定大小的线程池,可控制并发的线程数,超出的线程会在队列中等待;
(固定大小的线程池)
2. Executors.newCachedThreadPool:创建⼀个可缓存的线程池,若线程数超过处理所需,缓存⼀段时间后会回收,若线程数不够,则新建线程;
(线程可缓存,并且线程数随着任务量而定)
3. Executors.newSingleThreadExecutor:创建单个线程数的线程池,它可以保证先进先出的执⾏顺序;
(单个线程的线程池)
4. Executors.newScheduledThreadPool:创建⼀个可以执⾏延迟任务的线程池;
(可以执行延迟任务的线程池)
5. Executors.newSingleThreadScheduledExecutor:创建⼀个单线程的可以执⾏延迟任务的线程池;
(单线程可以执行延迟任务的线程池)
6. Executors.newWorkStealingPool:创建⼀个抢占式执⾏的线程池(任务执⾏顺序不确定)【JDK1.8 添加】。
(根据当前服务器的CPU创建线程池)
7. ThreadPoolExecutor:最原始的创建线程池的⽅式,它包含了 7 个参数可供设置,后⾯会详细讲。
(手动创建线程池)

4.1固定数量的线程池

ExecutorService threadPool = Executors.newFixedThreadPool(2);

4.1.1execute VS submit

import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

/**
 *  创建一个固定大小的线程池
 */
public class ThreadPollDemo1 {
    public static void main(String[] args) {
        //1.创建一个包含5个线程的线程池
        ExecutorService threadPool = Executors.newFixedThreadPool(5);
        //2.使用线程池执行任务
        //使用线程池执行任务二
        for (int i = 0; i < 10; i++) {
            //给线程池添加任务
            threadPool.execute(new Runnable() {
                @Override
                public void run() {
                    System.out.println("线程名称:" + Thread.currentThread().getName());
                }
            });
        }

    }
}

在这里插入图片描述

/**
 * 演示有返回值的线程池
 */

import java.util.Random;
import java.util.concurrent.*;

public class ThreadPollDemo2 {
    public static void main(String[] args) throws ExecutionException, InterruptedException {
        ExecutorService threadPool = Executors.newFixedThreadPool(5);
        Future<Integer> result = threadPool.submit(new Callable<Integer>() {
            @Override
            public Integer call() throws Exception {
                int num = new Random().nextInt();
                System.out.println("生成随机数:" + num);
                return num;
            }
        });
        System.out.println("得到线程池返回结果:" + result.get());
    }
}

在这里插入图片描述
得到结果:
向线程池中添加任务的方式有两种:

a. execute:只能执行不带返回值的任务;
b. submit:它可以执行有返回值的任务或者是没有返回值的任务

查看源码:
在这里插入图片描述
在这里插入图片描述

4.1.2线程工厂

作用:为线程池提供线程的创建。
提供的功能:
1.设置(线程池中的)线程命名规则
2.设置线程的优先级
3.设置线程分组
4.设置线程类型(用户线程和守护线程)

import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.ThreadFactory;

import static java.lang.Thread.MAX_PRIORITY;

/**
 * 线程工厂的示例
 */
public class ThreadPollDemo3 {
    public static void main(String[] args) {
        //1.创建线程工厂
        ThreadFactory factory = new ThreadFactory() {
            @Override
            public Thread newThread(Runnable r) {
                Thread thread = new Thread(r);
                //设置线程的命名规则
                thread.setName("我的线程-" + r.hashCode());
                //设置线程的优先级
                thread.setPriority(MAX_PRIORITY);
                return thread;
            }
        };
        ExecutorService service = Executors.newFixedThreadPool(5,factory);
        for (int i = 0; i < 5; i++) {
            service.submit(() -> {
                //任务
                System.out.println("线程池开始执行了:" + Thread.currentThread().getName());
            });
        }
    }
}

在这里插入图片描述
注意事项:
一定要将任务传入这个线程工厂,否则就会出错。
在这里插入图片描述
如果什么都不传,就会什么都打印不出来
在这里插入图片描述

4.2带缓存的线程池

ExecutorService threadPool =
Executors.newCachedThreadPool(threadFactory);

线程池会根据任务数创建线程池,并且在一定时间内可以重复使用这些线程。
示例:

import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

/**
 * 带缓存的线程池
 */
public class ThreadPollDemo4 {
    public static void main(String[] args) {
        //创建线程池
        ExecutorService service = Executors.newCachedThreadPool();
        for (int i = 0; i < 1000; i++) {
            int finalI = i;
            service.submit(() -> {
                System.out.println("i: "+ finalI +"|线程名称:"+Thread.currentThread().getName());
            });
        }
    }
}

在这里插入图片描述
有1000个任务并不会创建1000个线程,通过执行可以发现最多会创建100多个线程,然后进行复用。

4.3执行定时任务的线程池

4.3.1延迟执行(1次)

import java.time.LocalDateTime;
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;

/**
 * 创建执行定时任务的线程池
 */
public class ThreadPollDemo5 {
    public static void main(String[] args) {
        //创建线程池
        ScheduledExecutorService service = Executors.newScheduledThreadPool(5);
        System.out.println("添加任务的时间:" + LocalDateTime.now());
        //执行定时任务(延迟3s)
        service.schedule(new Runnable() {
            @Override
            public void run() {
                System.out.println("执行了任务:"+ LocalDateTime.now());
            }
        },3, TimeUnit.SECONDS);
    }
}

在这里插入图片描述
创建这个线程池时有3个参数:
a.执行任务
b.延迟 n秒执行
c.配合2执行的时间单位

4.3.2固定频率执行

可以让线程以以固定频率间隔n秒执行,创建这个线程池有四个参数:
a.执行任务
b.延迟n秒执行
c.执行定时任务的频率
d.配合3执行的时间单位

4.3.2.1scheduleAtFixedRate

import java.time.LocalDateTime;
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;

/**
 * 创建执行定时任务的线程池
 */
public class ThreadPollDemo5 {
    public static void main(String[] args) {
        //创建线程池
        ScheduledExecutorService service = Executors.newScheduledThreadPool(5);
        System.out.println("添加任务的时间:" + LocalDateTime.now());
        ///2s之后开始执行定时任务,定时任务每隔4s执行一次
        service.scheduleAtFixedRate(new Runnable() {
            @Override
            public void run() {
                System.out.println("执行任务:"+LocalDateTime.now());
            }
        },2,4,TimeUnit.SECONDS);
    }
}

在这里插入图片描述

4.3.2.2scheduleWithFixedDelay

import java.time.LocalDateTime;
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;

/**
 * 创建执行定时任务的线程池
 */
public class ThreadPollDemo5 {
    public static void main(String[] args) {
        //创建线程池
        ScheduledExecutorService service = Executors.newScheduledThreadPool(5);
        System.out.println("添加任务的时间:" + LocalDateTime.now());
        //2s之后开始执行定时任务,定时任务每隔4s执行一次
        service.scheduleWithFixedDelay(new Runnable() {
            @Override
            public void run() {
                System.out.println("执行时间:"+LocalDateTime.now());
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        },2,4,TimeUnit.SECONDS);

    }
}

在这里插入图片描述

两个方法的区别:
scheduleAtFixedRate是以上一次任务开始的时间,加上任务的执行周期,作为下一次定时任务的开始时间。
scheduleWithFixedDelay是以上一次任务的结束时间,加上任务的执行周期,作为下次定时任务的开始时间。

所以,scheduleAtFixedRate执行任务为4s,而scheduleWithFixedDelay执行任务周期为5s

注意事项:
在scheduleAtFixedRate中,有时任务的执行时间大于延迟任务设定的时间间隔,那么当任务执行完之后才会开始执行下次任务,此时并不是以设定执行周期来执行任务。

import java.time.LocalDateTime;
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;

/**
 * 创建执行定时任务的线程池
 */
public class ThreadPollDemo5 {
    public static void main(String[] args) {
        //创建线程池
        ScheduledExecutorService service = Executors.newScheduledThreadPool(5);
        System.out.println("添加任务的时间:" + LocalDateTime.now());

        //2s之后开始执行定时任务,定时任务每隔4s执行一次
        service.scheduleAtFixedRate(new Runnable() {
            @Override
            public void run() {
                System.out.println("执行任务:"+LocalDateTime.now());
                try {
                    Thread.sleep(5*1000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        },2,4,TimeUnit.SECONDS);
    }
}

在这里插入图片描述

4.4定时任务的单线程的线程池

import java.time.LocalDateTime;
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;

public class ThreadPollDemo6 {
    public static void main(String[] args) {
        //创建单线程线程池
        ScheduledExecutorService service = Executors.newSingleThreadScheduledExecutor();
        System.out.println("添加任务的时间:" + LocalDateTime.now());
        service.schedule(new Runnable() {
            @Override
            public void run() {
                System.out.println("任务执行:" + LocalDateTime.now());
            }
        },2, TimeUnit.SECONDS);
    }
}

在这里插入图片描述

4.5单线程线程池

import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

public class ThreadPollDemo7 {
    public static void main(String[] args) {
        ExecutorService service = Executors.newSingleThreadExecutor();
        for (int i = 0; i < 5; i++) {
            service.submit(new Runnable() {
                @Override
                public void run() {
                    System.out.println("线程名" + Thread.currentThread().getName());
                }
            });
        }
    }
}

在这里插入图片描述
为什么不直接使用线程?
答:首先因为他是线程池,所有它能够提供任务队列和任务管理的功能,防止内存溢出。
其次它可以自定义拒绝策略(本文最后会讲到拒绝策略)

4.6根据当前CPU生成线程池

import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

/**
 * 根据当前设备的配置自动生成线程池
 */
public class ThreadPollDemo8 {
    public static void main(String[] args) {
        ExecutorService executorService = Executors.newWorkStealingPool();
        for (int i = 0; i < 100; i++) {
            int finalI = i;
            executorService.submit(() -> {
                System.out.println("任务:" + finalI + "线程名:" + Thread.currentThread().getName());
            });
        }
        while (!executorService.isTerminated()) {};
    }
}

在这里插入图片描述

4.7 ThreadPoolExecutor

前六种创建线程池的方式是自动创建的,而这第7种方式是手动创建的,那为什么有了自动创建的方式却还需要手动创建呢?
答:那是因为在阿里手册里有强制规定,并且自动创建线程池有缺陷存在。

在这里插入图片描述

4.7.1参数说明

在这里插入图片描述
corePoolSize:核心线程数
maximumPoolSize:最大的线程数(包含核心线程数)
keepAliveTime:空闲线程的存活时间
TimeUnit unit:参数3的时间单位描述
BlockingQueue:任务队列,用于存储线程池的待执行任务的
ThreadFactory:线程工厂,用于生成线程
handler:拒绝管理器(处理极端问题,当任务队列都满了之后会采取的措施)

4.7.2具体实现

import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.ThreadFactory;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;

public class ThreadPollDemo9 {
    public static void main(String[] args) {
        ThreadFactory factory = new ThreadFactory() {
            @Override
            public Thread newThread(Runnable r) {
                Thread thread = new Thread(r);
                return thread;
            }
        };


        //手动方式创建线程池
        ThreadPoolExecutor executor =
                new ThreadPoolExecutor(5,10,10, TimeUnit.SECONDS,
                        new LinkedBlockingQueue<>(100),factory,
                        new ThreadPoolExecutor.DiscardPolicy());
        for (int i = 0; i < 10; i++) {
            executor.submit(() -> {
                System.out.println("线程名称:"+Thread.currentThread().getName());
            });
        }
    }
}

在这里插入图片描述

4.7.3线程池的执行流程

在这里插入图片描述
关键执行步骤:
1.当任务来了之后,判断线程池中实际线程数是否小于核心线程数,如果小于就直接创建线程并执行任务。

2.当实际线程数大于核心线程数,它会判断任务队列是否已满,如果未满直接将任务存放队列即可。

3.判断线程池的实际线程数是否大于最大线程数,如果小于最大线程数直接创建线程执行任务;实际线程数已经等于最大线程数,那么会直接执行拒绝策略。

五、拒绝策略

1.AbortPolicy
提示异常拒绝执行,是默认的拒绝策略:

import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.ThreadFactory;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;

public class ThreadPollDemo9 {
    public static void main(String[] args) {
        ThreadFactory factory = new ThreadFactory() {
            @Override
            public Thread newThread(Runnable r) {
                Thread thread = new Thread(r);
                return thread;
            }
        };


        //手动方式创建线程池
        ThreadPoolExecutor executor =
                new ThreadPoolExecutor(2,2,10, TimeUnit.SECONDS,
                        new LinkedBlockingQueue<>(2),factory,
                        new ThreadPoolExecutor.AbortPolicy());
        for (int i = 0; i < 5; i++) {
            executor.submit(() -> {
                System.out.println("线程名称:"+Thread.currentThread().getName());
            });
        }
    }
}

在这里插入图片描述

2.DiscardPolicy
忽略最新的任务,不报错:

import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.ThreadFactory;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;

public class ThreadPollDemo9 {
    public static void main(String[] args) {
        ThreadFactory factory = new ThreadFactory() {
            @Override
            public Thread newThread(Runnable r) {
                Thread thread = new Thread(r);
                return thread;
            }
        };


        //手动方式创建线程池
        ThreadPoolExecutor executor =
                new ThreadPoolExecutor(2,2,10, TimeUnit.SECONDS,
                        new LinkedBlockingQueue<>(2),factory,
                        new ThreadPoolExecutor.DiscardPolicy());
        for (int i = 0; i < 5; i++) {
            int finalI = i;
            executor.submit(() -> {
                try {
                    Thread.sleep(100*finalI);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                System.out.println("执行任务:"+ finalI);
            });
        }
    }
}

在这里插入图片描述
3.DiscardOldestPolicy
忽略旧任务(阻塞队列里面的第一个任务):

import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.ThreadFactory;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;

public class ThreadPollDemo9 {
    public static void main(String[] args) {
        ThreadFactory factory = new ThreadFactory() {
            @Override
            public Thread newThread(Runnable r) {
                Thread thread = new Thread(r);
                return thread;
            }
        };


        //手动方式创建线程池
        ThreadPoolExecutor executor =
                new ThreadPoolExecutor(2,2,10, TimeUnit.SECONDS,
                        new LinkedBlockingQueue<>(2),factory,
                        new ThreadPoolExecutor.DiscardOldestPolicy());
        for (int i = 0; i < 5; i++) {
            int finalI = i;
            executor.submit(() -> {
                try {
                    Thread.sleep(100*finalI);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                System.out.println("执行任务:"+ finalI);
            });
        }
    }
}

在这里插入图片描述

4.CallerRunsPolicy
如果任务被拒绝了,则由调用线程(提交任务的线程)直接执行此任务。

import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.ThreadFactory;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;

public class ThreadPollDemo9 {
    public static void main(String[] args) {
        ThreadFactory factory = new ThreadFactory() {
            @Override
            public Thread newThread(Runnable r) {
                Thread thread = new Thread(r);
                return thread;
            }
        };


        //手动方式创建线程池
        ThreadPoolExecutor executor =
                new ThreadPoolExecutor(2,2,10, TimeUnit.SECONDS,
                        new LinkedBlockingQueue<>(2),factory,
                        new ThreadPoolExecutor.CallerRunsPolicy());
        for (int i = 0; i < 5; i++) {
            int finalI = i;
            executor.submit(() -> {
                try {
                    Thread.sleep(100*finalI);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                System.out.println(Thread.currentThread().getName()+"--执行任务:"+ finalI);
            });
        }
    }
}

在这里插入图片描述
5.自定义拒绝策略

import java.util.concurrent.*;

public class ThreadPollDemo9 {
    public static void main(String[] args) {
        ThreadFactory factory = new ThreadFactory() {
            @Override
            public Thread newThread(Runnable r) {
                Thread thread = new Thread(r);
                return thread;
            }
        };


        //手动方式创建线程池
        ThreadPoolExecutor executor =
                new ThreadPoolExecutor(2, 2, 10, TimeUnit.SECONDS,
                        new LinkedBlockingQueue<>(2), factory,
                        new RejectedExecutionHandler() {
                            @Override
                            public void rejectedExecution(Runnable r, ThreadPoolExecutor executor) {
                                //自定义的拒绝策略
                                System.out.println("我执行了自定义的拒绝策略");
                            }
                        });
        for (int i = 0; i < 5; i++) {
            int finalI = i;
            executor.submit(() -> {
                try {
                    Thread.sleep(100*finalI);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                System.out.println(Thread.currentThread().getName()+"--执行任务:"+ finalI);
            });
        }
    }
}

在这里插入图片描述

六、线程池的状态

在这里插入图片描述
分为五种:
1.RUNNING:线程池创建之后的状态,这种状态下可以执行任务;
2.SHUTDOWN:该状态下线程池不再接受新任务,但是会将工作队列中的任务执行结束
3.STOP:该状态下线程池不再接受新任务,不会处理工作队列中的任务,并且会中断线程;
4.TIDYING:该状态下所有任务都已终止,将会执行terminated方法;
5.TEIMINATED:执行完terminated方法之后。

Shutdown VS ShutdownNow:
1.shutdown执行时线程池终止接收新任务,并且会将任务队列中的任务处理完;
2.shutdoNow执行时线程池终止接收新任务,并且会终止执行任务队列中的任务。

  • 4
    点赞
  • 29
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值