线程池的创建方式
1.线程池的创建方式1:创建固定个数的线程池
(任务数趋向无限大、建议谨慎使用)
//创建固定个数的线程池
ExecutorService service = Executors.newFixedThreadPool(10);
for (int i = 0; i < 2 ; i++) {
//执行任务
service.execute(new Runnable() {
@Override
public void run() {
System.out.println("线程名:" +
Thread.currentThread().getName());
}
});
}
经典易错题:创建了10个线程来执行2个任务,问当前程序创建了几个线程?
答:2个
线程池的执行流程:当拿到一个任务之后,会判断当前线程池里面的线程数量是否达到了最大值,如果没有达到创建新的线程执行任务。当任务来了之后,线程池的线程数量已经是最大值,并且没有空闲线程,当前的线程会被发放到任务队列里面等待执行
线程池包含的两个重要内容:
1:命名
2:任务队列
自定义线程池的行为:(设置线程池的命名,线程池的优先级)
//线程工厂
MyThreadFactory myThreadFactory = new MyThreadFactory();
//创建线程池
ExecutorService service =
Executors.newFixedThreadPool(10,myThreadFactory);
2.线程池的创建方式2:创建带缓存的线程池
//创建带缓存的线程池
ExecutorService service = Executors.newCachedThreadPool();
适用场景:根据任务的数量生成对应的线程数,适用于大量的短期任务的时候
3.线程池的创建方式3:创建一个执行定时任务的线程池
方法1:service.scheduleWithFixedDelay
(以上一个任务的结束时间作为开始时间)
//执行任务
service.scheduleWithFixedDelay(new Runnable() {
@Override
public void run() {
System.out.println("执行任务:"+new Date());
}
},1,3, TimeUnit.SECONDS);//延迟1s开始执行定时任务,每隔3s执行一次
}
service.scheduleWithFixedDelay的四个参数:
参数1:线程池的任务
参数2:定时任务延迟多长时间开始执行
参数3:定时任务执行频率
参数4:配合参数2和参数3使用的时间单位
方法2:service.schedule
service.schedule(new Runnable() {
@Override
public void run() {
System.out.println("执行任务:"+new Date());
}
},3, TimeUnit.SECONDS);//即时每隔3s执行一次
schedule区别:
1:没有延迟执行的时间设置
2:定时任务只能执行一次
方法3:service.scheduleAtFixedRate
//执行任务
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);
}
方法3:scheduleAtFixedRate:开始时间是以上次任务的开始时间做起始时间的(固定频率执行)
方法1:service.scheduleWithFixedDelay:任务开始时间是以上次任务执行的结束时间作为开始时间的(时间不固定,根据任务执行的时间来定)
4.线程池的创建方式4:创建单个执行定时任务的线程池
//创建单个执行定时任务的线程池
ScheduledExecutorService service =
Executors.newSingleThreadScheduledExecutor();
5.线程池的创建方式5:创建单个线程的线程池
//创建单个线程的线程池
ScheduledExecutorService service =
Executors.newSingleThreadScheduledExecutor();
//执行任务(一个线程执行多个任务)
for (int i = 0; i < 10; i++) {
service.execute(new Runnable() {
@Override
public void run() {
System.out.println("线程名:"+
Thread.currentThread().getName());
}
});
}
经典面试题:单个线程的线程池有什么意义?
答:1.无需频繁的创建和销毁线程
2.可以更好地分配和管理以及存储任务(将任务存放在任务队列)
6线程池的创建方式6(JDK8+):根据当前的工作环境(CPU核心数、任务量)生成对应的线程池 异步线程池
synchronized:同步
同步:按照某种规则按序执行就叫同步
异步:根据当前工作环境来创建线程
同步执行的流程:
1.main调用线程池
2.线程池执行完之后
3.关闭线程池,main也会随之关闭
异步执行的流程:
1.main调用异步线程池
2.异步线程池后台执行,对于main线程来说异步线程池已经执行完成,关闭main线程
7线程池的创建方式7:
前六种Executors创建线程池可能存在的的问题:
1.线程数量不可控( Integet.MAX_VALUE)(线程的过度切换和争取 -> 程序执行比较慢)
2.任务数量不可控(任务数无限大 Integet.MAX_VALUE),当前任务量比较大的情况下就会造成内存溢出异常(OOM)
//原始的创建线程池的方法
ThreadPoolExecutor executor =
new ThreadPoolExecutor(5,10,
60, TimeUnit.SECONDS,
new LinkedBlockingDeque<>(1000));
for (int i = 0; i < 2 ; i++) {
executor.execute(new Runnable() {
@Override
public void run() {
System.out.println("线程名:" +Thread.currentThread().getName());
}
});
}
线程池创建线程是懒加载,当执行任务的时候才创建线程,并不是new的时候
1、问:假设:核心线程数:5 最大线程数:5 任务队列:5
当任务 = 11:
第一步:11-5 = 6
第二步:6-5 = 1
第三步:最大线程数-线程池的线程数量 = 0;线程池执行拒绝策略
拒绝策略:
(1)自定义拒绝策略
ThreadPoolExecutor executor = new ThreadPoolExecutor(
5, 5, 0, TimeUnit.SECONDS,
new LinkedBlockingDeque<>(5),
new RejectedExecutionHandler() {
@Override
public void rejectedExecution(Runnable r, ThreadPoolExecutor executor) {
//自 定义拒绝策略
System.out.println("执行了自定义拒绝策略");
}
});
(2)JDK四种策略:
- 默认的拒绝策略
- 使用线程池的线程来执行任务,使用主线程来执行任务
- new ThreadPoolExecutor.DiscardPolicy()
忽略新的任务
new ThreadPoolExecutor.DiscardOldestPolicy()
忽略老任务
线程池的两种执行方式:
1、execute执行(new Runnable)无返回值的
2、submit执行(new Runnable无返回值/new Callable有返回值)
//返回返回值
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());
}
-
区别:
1、execute只能执行Runnable任务,他是无返回值的;submit他既能执行Runnable无返回值的任务,也可以执行Callable有返回值的任务
2、execute知心人跟吴如果有OOM异常 会将异常打印到控制台;submit执行任务出现OOM异常 不会打印异常 -
线程池的特征:
线程池相比于线程来说是长生命特征的,即使没有任务也会运行并等待执行任务 -
线程池的关闭:
1、executor.shutdown() :拒绝新任务加入,等待线程池中的任务队列执行完后,再停止线程池
2、executor.shutdownNow(): 拒绝执行新任务,不会等待任务队列中的任务执行完成,就停止线程池 -
线程池状态:(只是给开发者可见的 ,客户机是不可见的)
注意:线程池状态不是线程状态(6种)
ThreadLocal:
1、set()将变量存放在线程中
2、get()从线程中取得私有变量
3、remove()从线程中移除私有变量
问:什么时候不会执行initialValue?为什么不会执行?
- set之后就不执行了 ThreadLocal是懒加载的,当调用了get()方法之后,
初始化方法2:withInitial
1、
static ThreadLocal<String> threadLocal =
ThreadLocal.withInitial(new Supplier<String>() {
@Override
public String get() {
System.out.println("执行了初始化方法");
return "Java";
}
});
public static void main(String[] args) {
String result = threadLocal.get();
System.out.println("结果是:" + result);
}
2、
static ThreadLocal<String> threadLocal =
ThreadLocal.withInitial(() -> "Java");
public static void main(String[] args) {
String result = threadLocal.get();
System.out.println("结果是:" + result);
}
ThreadLocal 使用场景:
1、解决线程安全问题
2、实现线程级别的数据传递
(以下示例)
public static void main(String[] args) {
//模拟用户的登录
User user = new User();
user.setUsername("Java");
ThreadLocalUtil.threadLocal.set(user);
//调用订单系统
Order order = new Order();
order.getOrder();
//调用日志系统
Logger logger = new Logger();
logger.addLog();
}
static class ThreadLocalUtil{
static ThreadLocal<User> threadLocal = new ThreadLocal<>();
}
static class User{
private String username;
public void setUsername(String username) {
this.username = username;
}
public String getUsername() {
return username;
}
}
/*
* 日志系统*/
static class Logger {
public void addLog(){
//得到用户信息
User user = ThreadLocalUtil.threadLocal.get();
//
System.out.println("添加日志:"+user.getUsername());
}
}
static class Order{
public void getOrder(){
User user = ThreadLocalUtil.threadLocal.get();
//
System.out.println("订单列表:" + user.getUsername());
}
}
ThreadLocal缺点:
1:不可继承(子线程不能访问父线程的值)
static ThreadLocal<String> threadLocal = new ThreadLocal<>();
public static void main(String[] args) {
//set ThreadLocal
threadLocal.set("Java");
Thread t1 = new Thread(new Runnable() {
@Override
public void run() {
//get ThreadLocal
String result = threadLocal.get();
System.out.println("结果是:" + result);
}
});
t1.start();
使用ThreadLocal.InheritableThreadLocal可继承:但是同级线程不可继承
2:脏读(脏数据):在一个线程内读到了不属于自己的数据
- 线程使用ThreadLocal 不会出现脏读 -> 每个线程都是用自己的变量值和ThreadLocal
- 线程池里面使用 ThreadLocal 就会出现脏读,线程池会复用线程,复用线程之后,也会复用线程中的静态属性,从而导致了某些方法不会被执行,于是就出现了脏数据问题
脏数据的解决方案:
- 避免使用静态属性(静态属性在线程池中会复用)
- 使用remove
3:内存溢出问题:(最常出现的问题)、打开数据连接但未关闭
内存溢出:当一个线程执行完之后,不会释放这个线程所占用的内存,或者释放内存不及时的情况都叫做内存溢出。->线程不用了,但线程相关的内存还得不到相关的释放