线程的缺点
1.线程的创建需要开辟内存资源:本地方法栈、虚拟机栈、程序计数器等线程私有变量的内存。频繁的创建和销毁,会带来一定的性能的开销。
2、使用线程不能很好的管理任务和友好的拒绝任务。
所有就引入了线程池这一技术。
线程池
线程池定义:使用池化技术来管理和使用线程的技术。这种技术就叫线程池。
线程池里面的重要内容:
1、线程
2、任务队列
线程池的执行流程:当拿到一个任务之后,会判断当前线程池里面的数量是否达到了最大值,如果没有达到创建新的线程执行任务;当任务来了之后,线程池的线程数量已经是最大值,并且没有空闲的线程,当前的任务会被放到线程池的任务队列里等待。
线程池的七种创建方式
第一种:创建固定个数的线程池。(任务数趋向无限大,建议谨慎使用)
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
public class ThreadPoolDemo1 {
public static void main(String[] args) {
//创建固定个数的线程池
ExecutorService service = Executors.newFixedThreadPool(10);
//执行任务
for (int i = 0; i <10 ; i++) {
service.execute(new Runnable() {
@Override
public void run() {
System.out.println("线程名:"+Thread.currentThread().getName());
}
});
}
}
}
在这种创建方式的基础上,创建了10个线程来执行2个任务,问当前创建了几个线程?
答:启动了两个线程。因为线程池创建线程是懒加载,再有任务的时候才会去创建线程,并不是new的时候就创建线程。
自定义线程池行为(设置线程池命名规则)
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.ThreadFactory;
public class ThreadPoolDemo2 {
public static void main(String[] args) {
//线程工厂
MyThreadFactory myThreadFactory = new MyThreadFactory();
ExecutorService service = Executors.newFixedThreadPool(10,myThreadFactory);
for (int i = 0; i <10 ; i++) {
service.execute(new Runnable() {
@Override
public void run() {
System.out.println("线程名:" + Thread.currentThread().getName()+"线程优先级" + Thread.currentThread().getPriority());
}
});
}
}
private static int count;
static class MyThreadFactory implements ThreadFactory{
@Override
public Thread newThread(Runnable r) {
Thread thread = new Thread(r);
//设置线程池的命名
thread.setName("myThreadPoll-"+count++);
//设置线程的优先级
thread.setPriority(10);
return thread;
}
}
}
第二种:创建带缓存的线程池(根据任务的数量生成对应的线程数,它适用于大量短期任务)
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
public class ThreadPoolDemo3 {
public static void main(String[] args) {
//创建带缓存的线程池
ExecutorService service = Executors.newCachedThreadPool();
for (int i = 0; i <10 ; i++) {
service.execute(new Runnable() {
@Override
public void run() {
System.out.println("线程名:"+Thread.currentThread().getName());
}
});
}
}
}
第三种:创建可以执行定时任务的线程池(三种方式)
import java.util.Date;
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;
public class ThreadPoolDemo4 {
public static void main(String[] args) {
//创建一个执行定时任务的线程池
ScheduledExecutorService service = Executors.newScheduledThreadPool(10);
System.out.println("执行任务之前:"+new Date());
//执行任务
service.scheduleWithFixedDelay(new Runnable() {
@Override
public void run() {
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("执行任务:"+new Date());
}
},1,3, TimeUnit.SECONDS);
}
}
参数列表:
参数1:线程池的任务
参数2:定时任务延迟多长时间开始执行
参数3:定时执行任务的执行频率
参数4:配合参数2和3的时间单位
import java.util.Date;
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;
public class ThreadPoolDemo5 {
public static void main(String[] args) {
//创建一个执行定时任务的线程池
ScheduledExecutorService service = Executors.newScheduledThreadPool(10);
System.out.println("执行任务之前:"+new Date());
//执行任务
service.schedule(new Runnable() {
@Override
public void run() {
System.out.println("执行任务:"+new Date());
}
},3, TimeUnit.SECONDS);
}
}
schedule相比于scheduleWithFixedDelay的区别:
1、没有延迟执行的时间设置。
2、定时任务只能执行一次。
import java.util.Date;
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;
public class ThreadPoolDemo6 {
public static void main(String[] args) {
//创建一个执行定时任务的线程池
ScheduledExecutorService service = Executors.newScheduledThreadPool(10);
System.out.println("执行任务之前:"+new Date());
//执行任务
service.scheduleAtFixedRate(new Runnable() {
@Override
public void run() {
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("执行任务:"+new Date());
}
},1,3, TimeUnit.SECONDS);
}
}
scheduleWithFixedDelay与scheduleAtFixedRate的区别:
scheduleWithFixedDelay:执行任务的开始时间是以上次任务结束时间作为开始时间的(时间不固定,根据任务执行的时间来定)
scheduleAtFixedRate:开始时间是以上次任务的开始时间做起始时间(固定时间)
第四种:创建单个执行定时任务的线程池
import java.util.Date;
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;
public class ThreadPoolDemo7 {
public static void main(String[] args) {
//创建单个执行定时任务的线程池
ScheduledExecutorService service = Executors.newSingleThreadScheduledExecutor();
service.scheduleAtFixedRate(new Runnable() {
@Override
public void run() {
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("执行任务:"+new Date());
}
},1,3, TimeUnit.SECONDS);
}
}
单个线程池有什么意义?
答:1、无需频繁的创建个销毁线程
2、可以更好地分配和管理以及存储任务(任务队列)
第五种:创建单个线程的线程池(频繁的创建和消耗、更好地分配和执行任务,并且可以将任务放到缓存队列中)
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
public class ThreadPoolDemo8 {
public static void main(String[] args) {
//创建单个线程的线程池
ExecutorService service = Executors.newSingleThreadExecutor();
//执行任务
for (int i = 0; i < 10; i++) {
service.execute(new Runnable() {
@Override
public void run() {
System.out.println("线程名:"+Thread.currentThread().getName());
}
});
}
}
}
第六种:(JDK8+)根据当前的工作环境(CPU核心数、任务量)创建异步线程池
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
public class ThreadPoolDemo9 {
public static void main(String[] args) {
//根据当前工作环境来创建线程
ExecutorService service = Executors.newWorkStealingPool();
for (int i = 0; i <10 ; i++) {
service.execute(new Runnable() {
@Override
public void run() {
System.out.println("线程名:"+Thread.currentThread().getName());
}
});
}
//等待异步线程池执行完成(根据线程池的终止状态来判断)
while(!service.isTerminated()){
}
}
}
synchronize:同步
同步:按照某种规则按序执行的就叫同步。
同步执行流程:
1、main调用线程池
2、线程池执行完之后
3、关闭线程池、mian也会随之关闭
异步执行的流程:
1、main调用异步线程池
2、异步线程池后台执行,对于main来说来说,异步线程池已经执行完成,关闭main线程
Executors创建线程的问题:
1、线程数量不可控(线程的过度切换和争取-》程序执行比较慢)
2、任务数量不可控(任务数无线大 Integer.MAX_VALLUE),当任务量比较大的情况下就会造成内存异常(OOM)。
所以就有了第七种创建方式,也是最实用的创建方式
第七种:原始的创建线程池的方法
import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
public class ThreadPoolDemo10 {
public static void main(String[] args) {
//原始的创建线程池的方法
ThreadPoolExecutor executor =
new ThreadPoolExecutor(5,10,60, TimeUnit.SECONDS,
new LinkedBlockingQueue<>(1000));//任务队列一定要设置容量
for (int i = 0; i < 5; i++) {
executor.execute(new Runnable() {
@Override
public void run() {
System.out.println("线程名:"+Thread.currentThread().getName());
}
});
}
}
}
ThreadPoolExecutor 优点:
a、解决了线程数量不可控的问题 b、解决了任务数不可控问题
ThreadPoolExecutor 参数:
1、核心线程数(正式员工)
2、最大线程数(正式员工+临时工)
3、生存时间(long)
4、时间单位
5、任务队列
6、线程工厂(优先级、命名、类型。。。)
7、拒绝策略
这里重点说一下拒绝策略:
拒绝策略:
JDK4种策略+自定义策略
1、默认的拒绝策略:不执行任务,抛出异常
import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
public class ThreadPoolDemo13 {
public static void main(String[] args) {
//创建线程池
ThreadPoolExecutor executor = new ThreadPoolExecutor(5,5,0, TimeUnit.SECONDS,
new LinkedBlockingQueue<>(5),new ThreadPoolExecutor.AbortPolicy());
for (int i = 0; i < 11; i++) {
int finall = i;
executor.execute(new Runnable() {
@Override
public void run() {
System.out.println("任务:"+finall+",线程名:"+Thread.currentThread().getName());
}
});
}
}
}
2、使用调用线程池的线程来执行任务:使用主线程来执行任务
import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
public class ThreadPoolDemo14 {
public static void main(String[] args) {
//创建线程池
ThreadPoolExecutor executor = new ThreadPoolExecutor(5,5,0, TimeUnit.SECONDS,
new LinkedBlockingQueue<>(5),new ThreadPoolExecutor.CallerRunsPolicy());
for (int i = 0; i < 11; i++) {
int finall = i;
executor.execute(new Runnable() {
@Override
public void run() {
System.out.println("任务:"+finall+",线程名:"+Thread.currentThread().getName());
}
});
}
}
}
3、忽略(新)任务:这里的任务的新旧指的是在任务队列里得任务。
import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
public class ThreadPoolDemo15 {
public static void main(String[] args) throws InterruptedException {
//创建线程池
ThreadPoolExecutor executor = new ThreadPoolExecutor(5,5,0, TimeUnit.SECONDS,
new LinkedBlockingQueue<>(5),new ThreadPoolExecutor.DiscardPolicy());
for (int i = 0; i < 11; i++) {
int finall = i;
executor.execute(new Runnable() {
@Override
public void run() {
System.out.println("任务:"+finall+",线程名:"+Thread.currentThread().getName());
}
});
}
}
}
这里可以看到没有执行任务10,任务时就是相对于任务队列里已经放满了的新任务。
4、忽略(老)任务:这里的任务的新旧指的是在任务队列里得任务。
import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
public class ThreadPoolDemo16 {
public static void main(String[] args) throws InterruptedException {
//创建线程池
ThreadPoolExecutor executor = new ThreadPoolExecutor(5,5,0, TimeUnit.SECONDS,
new LinkedBlockingQueue<>(5),new ThreadPoolExecutor.DiscardOldestPolicy());
for (int i = 0; i < 11; i++) {
int finall = i;
executor.execute(new Runnable() {
@Override
public void run() {
System.out.println("任务:"+finall+",线程名:"+Thread.currentThread().getName());
}
});
}
}
}
这里可以看到没有执行任务5,因为任务5是加入任务队列的第一个任务,这里指的就是旧任务。
5、自定义策略:
import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.RejectedExecutionHandler;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
public class ThreadPoolDemo60 {
public static void main(String[] args) throws InterruptedException {
// 创建线程池
ThreadPoolExecutor executor = new ThreadPoolExecutor(
5, 5,
0, TimeUnit.SECONDS,
new LinkedBlockingQueue<>(5),
new RejectedExecutionHandler() {
@Override
public void rejectedExecution(Runnable r, ThreadPoolExecutor executor) {
// 自定义拒绝策略
System.out.println("执行了自定义拒绝策略");
}
});
for (int i = 0; i < 11; i++) {
int finalI = i;
executor.execute(new Runnable() {
@Override
public void run() {
System.out.println("任务:" + finalI + ",线程名:" +
Thread.currentThread().getName());
}
});
// Thread.sleep(200);
}
}
}
线程池的优点:
1、避免频繁创建和消耗所带来的的性能开销
2、可以优化的拒绝任务
3、可以有更多的功能
线程池的两种执行方式:
1、execute(new Runnable)执行 无返回值得
2、submit 执行(new Runnable) 无返回值/ (new Callable) 有返回值
这里演示一下submit执行(new Callable):
import java.util.Random;
import java.util.concurrent.*;
public class ThreadPoolDemo17 {
public static void main(String[] args) throws ExecutionException, InterruptedException {
ThreadPoolExecutor executor = new ThreadPoolExecutor(5,5,0, TimeUnit.SECONDS,
new LinkedBlockingQueue<>(1000));
//返回返回值
Future<Integer> future = executor.submit(new Callable<Integer>() {
@Override
public Integer call() throws Exception {
int num = new Random().nextInt(10);
System.out.println("线程池生成了随机数" + num);
return num;
}
});
System.out.println("main得到返回值" + future.get());
}
}
可以看到主线程拿到了返回值8.
线程池的特征:
线程池相比于线程来说是长生命周期,即使没有任务了,也会运行并等待任务。
线程池的关闭:
1、executor.shutdown():拒绝新任务加入,等待线程池中的任务队列执行完之后再停止线程池。 SHUTDOWN状态
2、executor.shutdownNow():拒绝新任务加入,不会等待任务队列中的任务完成就会停止线程池。STOP状态
import java.util.Random;
import java.util.concurrent.*;
public class ThreadPoolDemo18 {
public static void main(String[] args) throws ExecutionException, InterruptedException {
ThreadPoolExecutor executor = new ThreadPoolExecutor(5,5,0, TimeUnit.SECONDS,
new LinkedBlockingQueue<>(1000));
//返回返回值
Future<Integer> future = executor.submit(new Callable<Integer>() {
@Override
public Integer call() throws Exception {
int num = new Random().nextInt(10);
System.out.println("线程池生成了随机数" + num);
return num;
}
});
System.out.println("main得到返回值" + future.get());
//关闭线程池
executor.shutdown();
//executor.shutdownNow();
}
}
线程池状态(5种)
注意:线程池状态不等于线程的状态(6种)
1、RUNNING 运行状态
2、SHUTDOWN 执行executor.shutdown()后的状态。该状态下线程池不再接受新任务,但是会将工作队列中的任务执行结束。
3、STOP 执行executor.shutdownNow()后的状态。该状态下线程池不再接受新任务,但是不会处理工作队列中的任务。
4、TIDYING : 销毁前的前置状态
5、TERMINATED 销毁状态
注意:线程池的状态只是给开发者使用的,客户机是不可见的。