学习JavaEE的日子 Day32 线程池

Day32 线程池

1.引入

一个线程完成一项任务所需时间为:

  1. 创建线程时间 - Time1
  2. 线程中执行任务的时间 - Time2
  3. 销毁线程时间 - Time3

2.为什么需要线程池(重要)

  1. 线程池技术正是关注如何缩短或调整Time1和Time3的时间,从而提高程序的性能。项目中可以把Time1,T3分别安排在项目的启动和结束的时间段或者一些空闲的时间段(降低资源消耗)
  2. 线程池不仅调整Time1,Time3产生的时间段,而且它还显著减少了创建线程的数目,提高线程的复用率
  3. 系统启动一个新线程的成本是比较高的,因为涉及与操作系统的交互,在这种情形下,使用线程池可以很好地提高性能,尤其是当程序中需要创建大量生存期很短暂的线程时,优先考虑使用线程池
  4. 提高响应速度。当任务到达时,任务可以不需要等到线程创建就能立即执行。
  5. 提高线程的可管理性。

面试题3:为什么要使用线程池

1.线程池可以优化创建线程和销毁线程的时间(可以将线程池的创建放在项目启动之初,线程池的销毁放在项目销毁的使用)
2.提高线程的复用率,因为系统创建线程的成本很高,在项目中不会出现显示创建线程
3.有效的控制项目中的线程目数,因为线程过多,会导致程序的性能直线下降

3.Java提供的线程池(了解即可)

一般不会使用这个,局限性太多

ExecutorService:线程池的接口

Executors:创建各种线程池的工具类

面试题1:Java自带的线程池有哪些?

1.单个线程的线程池

2.指定个数的线程池

3.可缓存的线程池(60s闲置时间)

4.延迟任务的线程池

面试题2:Java自带的线程池的缺点

单个线程的线程池 – 无界队列(可能会导致内存溢出)

指定个数的线程池 – 无界队列(可能会导致内存溢出)

可缓存的线程池(60s闲置时间) – 最大线程数Integer.MAX_VALUE(会导致项目中的线程个数不可控)

延迟任务的线程池 – 最大线程数Integer.MAX_VALUE(会导致项目中的线程个数不可控)

public class Test {
	
	public static void main(String[] args) {
		
		//创建单个线程的线程池
		//ExecutorService pool = Executors.newSingleThreadExecutor();
		//创建指定线程的线程池
		//ExecutorService pool = Executors.newFixedThreadPool(3);
		//创建可缓存线程的线程池,自动回收60s闲置线程
		ExecutorService pool = Executors.newCachedThreadPool();
		
		//循环创建任务对象,并提交给线程池
		for (int i = 1; i <= 100; i++) {
			//创建任务对象
			Task task = new Task(i);
			//提交任务
			pool.execute(task);
		}
		
		//关闭线程池
		pool.shutdown();
	}
}

class Task implements Runnable{
	private int i;
	public Task(int i) {
		this.i = i;
	}
	@Override
	public void run() {
		System.out.println(Thread.currentThread().getName() + "处理了第" + num + "个任务");
	}
}

4.深入源码

ExecutorService pool = Executors.newSingleThreadExecutor();

ExecutorService pool = Executors.newFixedThreadPool(3);

ExecutorService pool = Executors.newCachedThreadPool();

三种线程池底层都是ThreadPoolExecutor类的对象

-- 分析ThreadPoolExecutor类的构造方法源码--------------------------------
public ThreadPoolExecutor(
	int corePoolSize,		------------- 核心线程数量 
    int maximumPoolSize,	------------- 最大线程数量 
	long keepAliveTime,		------------- 闲置时间,作用于核心线程数与最大线程数之间的线程
	TimeUnit unit,			------------- keepAliveTime的时间单位(可以是毫秒、秒....)
	BlockingQueue<Runnable> workQueue, -- 任务队列
	ThreadFactory threadFactory, -------- 线程工厂
	RejectedExecutionHandler handler ---- 达到了线程界限和队列容量时的处理方案(拒绝策略)
) {}
执行步骤:
	1.创建线程池后
    2.任务提交后,查看是否有核心线程:
        3.1 没有 -> 就创建核心线程 -> 执行任务 -> 执行完毕后又回到线程池中
        3.2-> 查看是否有闲置核心线程:
        	4.1-> 执行任务 -> 执行完毕后又回到线程池
        	4.2 没有 -> 查看当前核心线程数是否核心线程数量:
        		5.1-> 就创建核心线程 -> 执行任务 -> 执行完毕后又回到线程池中
        		5.2-> 查看任务列表是否装载满:
        			6.1 没有 -> 就放入列表中,等待出现闲置线程
        			6.2 装满 -> 查看是否有普通线程(核心线程数到最大线程数量之间的线程)
        				7.1 没有 -> 就创建普通线程 -> 执行任务 -> 执行完毕后又回到线程池中
        				7.2-> 查看是否有闲置普通线程
        					7.1.1-> 执行任务 -> 执行完毕后又回到线程池中
        					7.1.2 没有 -> 查看现在所有线程数量是否为最大线程数:
        						8.1-> 执行处理方案(默认处理抛出异常)
        						8.2->就创建普通线程-> 执行任务 -> 执行完毕后又回到线程池中     			
注:
	1.为了更好的理解,在这里区分核心线程和普通线程,实际上区分的这么清楚,都是线程
    2.默认的处理方案就是抛出RejectedExecutionException

-- 分析单个线程的线程池的源码 --------------------------------
ExecutorService pool = Executors.newSingleThreadExecutor();
new ThreadPoolExecutor(
    1, -- 核心线程数量 
    1, -- 最大线程数量 
    0L, -- 闲置时间
    TimeUnit.MILLISECONDS, -- 时间单位(毫秒)
    new LinkedBlockingQueue<Runnable>() -- 无界任务队列,可以无限添加任务(内存溢出问题)
)  
-- 分析指定线程的线程池的源码 --------------------------------
ExecutorService pool = Executors.newFixedThreadPool(3);
new ThreadPoolExecutor(
    nThreads, -- 核心线程数量 
    nThreads, -- 最大线程数量 
    0L, -- 闲置时间
    TimeUnit.MILLISECONDS, -- 时间单位(毫秒)
    new LinkedBlockingQueue<Runnable>()-- 无界任务队列,可以无限添加任务(内存溢出问题)
)
-- 创建可缓存线程的线程池 -----------------------------------
new ThreadPoolExecutor(
    0, -- 核心线程数量 
    Integer.MAX_VALUE,-- 最大线程数量 
    60L, -- 闲置时间
    TimeUnit.SECONDS, -- 时间单位()
    new SynchronousQueue<Runnable>() -- 直接提交队列(同步队列):没有容量队列(最大线程数量是21亿多,太大了)

总结:核心线程满载 -> 任务队列 -> 普通线程 -> 拒绝策略

面试题5:线程池的调用顺序

  •  1.核心线程
    
  •  2.任务队列
    
  •  3.普通线程(最大线程数-核心线程数)
    
  •  4.拒绝策略
    

在这里插入图片描述

5.任务队列详解

注意:任务队列的分类、拒绝策略的分类

队列名称详解
LinkedBlockingQueue无界任务队列使用无界任务队列,线程池的任务队列可以无限制的添加新的任务,而线程池创建的最大线程数量就是你corePoolSize设置的数量,也就是说在这种情况下maximumPoolSize这个参数是无效的,哪怕你的任务队列中缓存了很多未执行的任务,当线程池的线程数达到corePoolSize后,就不会再增加了;若后续有新的任务加入,则直接进入队列等待,当使用这种任务队列模式时,一定要注意你任务提交与处理之间的协调与控制,不然会出现队列中的任务由于无法及时处理导致一直增长,直到最后资源耗尽的问题
SynchronousQueue 同步任务队列 直接提交任务队列使用直接提交任务队列,队列没有容量,每执行一个插入操作就会阻塞,需要再执行一个删除操作才会被唤醒,反之每一个删除操作也都要等待对应的插入操作。 任务队列为SynchronousQueue,创建的线程数大于maximumPoolSize时,直接执行了拒绝策略抛出异常。 使用SynchronousQueue队列,提交的任务不会被保存,总是会马上提交执行。如果用于执行任务的线程数量小于maximumPoolSize,则尝试创建新的线程,如果达到maximumPoolSize设置的最大值,则根据你设置的handler执行拒绝策略。因此这种方式你提交的任务不会被缓存起来,而是会被马上执行,在这种情况下,你需要对你程序的并发量有个准确的评估,才能设置合适的maximumPoolSize数量,否则很容易就会执行拒绝策略;
ArrayBlockingQueue有界任务队列使用有界任务队列,若有新的任务需要执行时,线程池会创建新的线程,直到创建的线程数量达到corePoolSize时,则会将新的任务加入到等待队列中。若等待队列已满,即超过ArrayBlockingQueue初始化的容量,则继续创建线程,直到线程数量达到maximumPoolSize设置的最大线程数量,若大于maximumPoolSize,则执行拒绝策略。在这种情况下,线程数量的上限与有界任务队列的状态有直接关系,如果有界队列初始容量较大或者没有达到超负荷的状态,线程数将一直维持在corePoolSize以下,反之当任务队列已满时,则会以maximumPoolSize为最大线程数上限。
PriorityBlockingQueue优先任务队列使用优先任务队列,它其实是一个特殊的无界队列,它其中无论添加了多少个任务,线程池创建的线程数也不会超过corePoolSize的数量,只不过其他队列一般是按照先进先出的规则处理任务,而PriorityBlockingQueue队列可以自定义规则根据任务的优先级顺序先后执行。

对优先队列的使用说明:

除了第一个任务直接创建线程执行外,其他的任务都被放入了优先任务队列,按优先级进行了重新排列执行,且线程池的线程数一直为corePoolSize,也就是只有一个。

5.1 无界队列

理解:这个队列没有上线

继承关系:LinkedBlockingQueue -> AbstractQueue -> AbstractCollection

小结:

1.LinkedBlockingQueue是Collection集合家族的一员

2.Collection集合家族(List、Set、Queue)

3.LinkedBlockingQueue数据结构单向链表

缺点:LinkedBlockingQueue可能造成内存溢出

public class Test01 {
	public static void main(String[] args) throws InterruptedException {
		
		//创建无界队列
		LinkedBlockingQueue<String> queue = new LinkedBlockingQueue<>();
		
		//添加元素
		queue.put("aaa");
		queue.put("bbb");
		queue.put("ccc");
		queue.put("ddd");
		queue.put("eee");
		queue.put("fff");
		
		//删除元素
		queue.remove("ccc");
		
		//遍历队列
		Iterator<String> it = queue.iterator();
		while (it.hasNext()) {
			String string = it.next();
			System.out.println(string);
		}
		
		
	}
}
public class Task implements Runnable,Comparable<Task>{

	private int priority;//优先级别
	
	public Task(int priority) {
		this.priority = priority;
	}

	@Override
	public void run() {
		System.out.println("任务被处理了");
	}

	//当前对象和其他对象做比较,当前优先级大就返回-1,优先级小就返回1,值越小优先级越高
	@Override
	public int compareTo(Task o) {
		return Integer.compare(this.priority, o.priority);
	}

	public int getPriority() {
		return priority;
	}
}
5.2 有界队列

继承关系:ArrayBlockingQueue -> AbstractQueue -> AbstractCollection

小结:

1.ArrayBlockingQueue数据结构一维数组

缺点:不能有效控制项目中的线程目数

public class Test02 {
	public static void main(String[] args) throws InterruptedException {
		
		//创建有界队列
		ArrayBlockingQueue<String> queue = new ArrayBlockingQueue<>(6);
		
		//添加元素
		queue.put("aaa");
		queue.put("bbb");
		queue.put("ccc");
		queue.put("ddd");
		queue.put("eee");
		queue.put("fff");
		
		//删除元素
		queue.remove("ccc");
		
		//遍历队列
		Iterator<String> it = queue.iterator();
		while (it.hasNext()) {
			String string = it.next();
			System.out.println(string);
		}
				
	}
}
5.3 优先队列

继承关系:ArrayBlockingQueue -> AbstractQueue -> AbstractCollection

ArrayBlockingQueue数据结构一维数组

public class Test03 {
	public static void main(String[] args) throws InterruptedException {
		
		//创建优先队列
		PriorityBlockingQueue<Task> queue = new PriorityBlockingQueue<>();//无参构造底层使用的是元素的内置比较器
		
		//添加元素
		queue.add(new Task(3));
		queue.add(new Task(1));
		queue.add(new Task(4));
		queue.add(new Task(2));
				
		//遍历取出队列
		while(!queue.isEmpty()){
			//删除第一元素
			Task poll = queue.poll();
			System.out.println(poll.getPriority());
		}		
		
	}
}

6.拒绝策略

ThreadPoolExecutor自带的拒绝策略有四种,都实现了RejectedExecutionHandler接口

比如:new ThreadPoolExecutor.AbortPolicy()

拒绝策略解释
AbortPolicy当有任务添加到线程池被拒绝时,会抛出RejectedExecutionException异常 线程池默认的拒绝策略。必须处理好抛出的异常,否则会打断当前的执行流程,影响后续的任务执行。
DiscardPolicy当有任务添加到线程池被拒绝时,直接丢弃,其他啥都没有
CallerRunsPolicy当有任务添加到线程池被拒绝时,线程池会将被拒绝的任务添加到线程池正在运行的线程中去运行。 一一般并发比较小,性能要求不高,不允许失败。但是,由于调用者自己运行任务,如果任务提交速度过快,可能导致程序阻塞,性能效率上必然的损失较大
DiscardOledestPolicy当有任务添加到线程池被拒绝时,线程池会丢弃阻塞队列中末尾的任务(最老的任务),然后将被拒绝的任务添加到末尾。 如果项目中有允许丢失任务的需求,可以使用

1.AbortPolicy:直接丢弃任务,抛出异常,这是默认策略

2.CallerRunsPolicy:只用调用者所在的线程来处理任务

3.DiscardOldestPolicy:丢弃等待队列中最老的任务,并执行当前任务

4.DiscardPolicy:直接丢弃任务,也不抛出异常

6.1 自定义拒绝策略(实现RejectedExecutionHandler接口)
public class Test {
	
	public static void main(String[] args) {
		
		ThreadPoolExecutor pool = new ThreadPoolExecutor(1, 1, 1000, TimeUnit.MILLISECONDS, new ArrayBlockingQueue<>(1), Executors.defaultThreadFactory(), new RejectedExecutionHandler() {
			@Override
			public void rejectedExecution(Runnable r, ThreadPoolExecutor executor) {
				System.out.println(r.toString()+"执行了拒绝策略");
			}
		});
		
		for (int i = 1; i <= 10; i++) {
			pool.execute(new Task());
		}
		
		pool.shutdown();
	}
}
class Task implements Runnable{
	
	@Override
	public void run() {
		try {
			Thread.sleep(1000);
			System.out.println(Thread.currentThread().getName());
		} catch (InterruptedException e) {
			e.printStackTrace();
		}
	}
}

7.自定义线程池原因(重要)

为什么不使用java自带的线程池?而去自定义线程池?

在《阿里巴巴java开发手册》中指出了线程资源必须通过线程池提供,不允许在应用中自行显示的创建线程,这样一方面使线程的创建更加规范,可以合理控制开辟线程的数量;另一方面线程的细节管理交给线程池处理,优化了资源的开销。

而线程池不允许使用Executors去创建,而要通过ThreadPoolExecutor方式,这一方面是由于jdk中Executor框架虽然提供了如newFixedThreadPool()、newSingleThreadExecutor()、newCachedThreadPool()等创建线程池的方法,但都有其局限性,不够灵活,而且有资源耗尽的风险(OOM - Out Of Memory )。

一般我们创建线程池时,为防止资源被耗尽,任务队列都会选择创建有界任务队列,但这种模式下如果出现任务队列已满且线程池创建的线程数达到你设置的最大线程数时,这时就需要你指定ThreadPoolExecutor的RejectedExecutionHandler参数即合理的拒绝策略,来处理线程池"超载"的情况

7.1 自定义线程池

七大属性要记住

前提学习线程池中如何创建线程:

线程池中线程就是通过ThreadPoolExecutor中的ThreadFactory,线程工厂创建的。那么通过自定义ThreadFactory,可以按需要对线程池中创建的线程进行一些特殊的设置,如命名、优先级等

public class Test01 {
	public static void main(String[] args) {
		
		ThreadPoolExecutor pool = new ThreadPoolExecutor(
				5, //核心线程数
				20, //最大线程数
				60, //闲置时间
				TimeUnit.SECONDS, //时间单位
				new ArrayBlockingQueue<>(30), //任务队列 -- 有界队列
				new ThreadFactory() {//自定义线程工厂
					private int num = 1;
					@Override
					public Thread newThread(Runnable r) {
						Thread t = new Thread(r);
						t.setName("自定义线程池中的线程"+num);
						t.setPriority(Thread.MAX_PRIORITY);
						num++;
						return t;
					}
				}, 
				new RejectedExecutionHandler() {//自定义拒绝策略
					@Override
					public void rejectedExecution(Runnable r, ThreadPoolExecutor executor) {
						System.out.println("任务被拒绝了~~~");
					}
				}
			);
		
		for (int i = 1; i <= 100; i++) {
			Task task = new Task(i);
			pool.execute(task);
		}
		
		
		pool.shutdown();
		
	}
}
public class Task implements Runnable{

	private int num;
	
	public Task(int num) {
		this.num = num;
	}

	@Override
	public void run() {
		System.out.println(Thread.currentThread().getName() + "处理了第" + num + "个任务");
	}

}


7.2 自己写一个线程池

自定义线程池类

线程工厂可以跟线程起名字

//自己写的线程池
public class FastThreadPool extends ThreadPoolExecutor{
	
	public FastThreadPool(int corePoolSize, int maximumPoolSize, long keepAliveTime, TimeUnit unit,
			BlockingQueue<Runnable> workQueue) {
		super(corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue, new FastThreadFactory(), new FastRejectedExecutionHandler());
	}

	public FastThreadPool(int corePoolSize, int maximumPoolSize, long keepAliveTime, TimeUnit unit,
			BlockingQueue<Runnable> workQueue, ThreadFactory threadFactory, RejectedExecutionHandler handler) {
		super(corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue, threadFactory, handler);
	}//七大属性记住,重要!!!
	
	@Override
	protected void beforeExecute(Thread t, Runnable r) {
		System.out.println("线程执行任务之前调用 -- " + r);
	}
	
	@Override
	protected void afterExecute(Runnable r, Throwable t) {
		System.out.println("线程执行任务之后调用 -- " + r);
	}
	
	@Override
	protected void terminated() {
		System.out.println("线程池关闭时调用");
	}
	
	//自定义线程工厂(实现ThreadFactory接口)
	private static class FastThreadFactory implements ThreadFactory{
		private int num;//线程编号
		@Override
		public Thread newThread(Runnable r) {
			Thread t = new Thread(r);
			t.setName("自定义线程池中的线程"+num);
			t.setPriority(Thread.MAX_PRIORITY);
			num++;
			return t;
		}
	}
	
	
	//自定义拒绝策略(实现RejectedExecutionHandler)
	private static class FastRejectedExecutionHandler implements RejectedExecutionHandler{
		@Override
		public void rejectedExecution(Runnable r, ThreadPoolExecutor executor) {
			System.out.println("任务被拒绝了~~~");
		}
	}

}


任务类

public class Task implements Runnable{

	private int num;
	
	public Task(int num) {
		this.num = num;
	}

	@Override
	public void run() {
		System.out.println(Thread.currentThread().getName() + "处理了第" + num + "个任务");
	}

	@Override
	public String toString() {
		return "任务" + num;
	}
}


测试类

public class Test01 {
	public static void main(String[] args) {
		
		FastThreadPool pool = new FastThreadPool(5, 20, 60, TimeUnit.SECONDS, new ArrayBlockingQueue<>(30));
		
		for (int i = 1; i <= 100; i++) {
			Task task = new Task(i);
			pool.execute(task);
		}
		
		//设置为true后,闲置时间一旦到达,核心线程也会被销毁
		//经验:我们一般不会回收核心线程,因为设置回收后线程池中的线程有可能为0,这样就没有线程复用率的说法了
		//pool.allowCoreThreadTimeOut(true);
		
		pool.shutdown();
		
	}
}


面试题:线程池中核心线程会被回收吗?

不会

但是当线程池调用了allowCoreThreadTimeOut(true);时,核心线程会被回收

但是一般不会调用该方法,也就是说,项目中线程池中的核心线程不要设计被回收,因为如果线程池中线程全部都回收了,就没有线程复用率这个说法

核心线程宁可闲置也不回收

7.3 ThreadPoolExecutor扩展

ThreadPoolExecutor扩展主要是围绕beforeExecute()、afterExecute()和terminated()三个方法的重写, 通过这三个方法我们可以监控每个任务的开始和结束时间,或者其他一些功能。下面我们可以通过代码实现一下

方法解释
beforeExecute线程池中任务运行前执行
afterExecute线程池中任务运行完毕后执行
terminated线程池退出后执行

上面自己写的自定义线程池里有相关使用

8.线程池线程数量

实际工作中使用 sysbench多线程性能测试工具 (重要),因为七大属性中的核心线程数,最大线程数,闲置时间不能随便乱写一个数字上去,而是要进行相关测试才能得出数据

9.带有返回值的任务类(实现 Callable<>接口)

注意:带有返回值的任务类和线程池一起使用

需求:计算任务,一个包含了2万个整数的数组,分拆了多个线程来进行并行计算,最后汇总出计算的结果。

public class Test01 {
	public static void main(String[] args) throws InterruptedException, ExecutionException {
		
		//创建数组
		int[] arr = new int[20000];
		
		//初始化数组数据 -- {1,2,3,....,20000}
		for (int i = 0; i < arr.length; i++) {
			arr[i] = i+1;
		}
		
		//创建线程池
		FastThreadPool pool = new FastThreadPool(4, 4, 0, TimeUnit.SECONDS, new ArrayBlockingQueue<>(5));
		
		//创建任务
		Task task1 = new Task(arr, 0, 5000,1);
		Task task2 = new Task(arr, 5000, 10000,2);
		Task task3 = new Task(arr, 10000, 15000,3);
		Task task4 = new Task(arr, 15000, 20000,4);
		
		//提交任务,任务完成后会返回Future对象,Future对象里存储了任务的返回值数据
		Future<Integer> future1 = pool.submit(task1);
		Future<Integer> future2 = pool.submit(task2);
		Future<Integer> future3 = pool.submit(task3);
		Future<Integer> future4 = pool.submit(task4);
		
		//合并任务返回值
		System.out.println(future1.get() + future2.get() + future3.get() + future4.get());
		
		
		pool.shutdown();
	}
}


带返回值的任务类

public class Task implements Callable<Integer>{
	
	private int[] arr;
	private int startIndex;
	private int endIndex;
	
	private int num;
	
	public Task(int[] arr, int startIndex, int endIndex,int num) {
		this.arr = arr;
		this.startIndex = startIndex;
		this.endIndex = endIndex;
		this.num = num;
	}

	//理解:线程抢到CPU资源后,才会调用call方法,call方法相当于Runnable接口中的run方法
	@Override
	public Integer call() throws Exception {
		
		int sum = 0;
		for (int i = startIndex; i < endIndex; i++) {
			sum += arr[i];
			System.out.println("任务" + num);
		}
		return sum;
	}

}


简答题

1.简述一下你对线程池的理解

(如果问到了这样的问题,可以展开的说一下线程池如何用、线程池的好处、线程池的启动策略)合理 利用线程池能够带来三个好处。

第一:降低资源消耗。通过重复利用已创建的线程降低线程创建和销毁造成的消耗。

第二:提高响应速度。当任务到达时,任务可以不需要等到线程创建就能立即执行。

第三:提高线程的可管理性。线程是稀缺资源,如果无限制的创建,不仅会消耗系统资源,还会降低系统的稳定性,使用线程池可以进行统一的分配,调优和监控。

2.线程池中 submit() 和 execute() 方法有什么区别?

接收参数:execute()只能执行 Runnable 类型的任务。submit()可以执行 Runnable 和 Callable 类型的任务。

返回值:submit()方法可以返回持有计算结果的 Future 对象,而execute()没有

异常处理:submit()方便Exception处理

3.你知道怎么创建线程池吗?

创建线程池的方式有多种,这里你只需要答 ThreadPoolExecutor 即可。

ThreadPoolExecutor() 是最原始的线程池创建,也是阿里巴巴 Java 开发手册中明确规范的创建线程池的方式。

ThreadPoolExecutor构造函数重要参数分析

七大参数。。。。

4.线程池的拒绝策略有哪些?

主要有4种拒绝策略:

1.AbortPolicy:直接丢弃任务,抛出异常,这是默认策略

2.CallerRunsPolicy:只用调用者所在的线程来处理任务

3.DiscardOldestPolicy:丢弃等待队列中最老的任务,并执行当前任务

4.DiscardPolicy:直接丢弃任务,也不抛出异常

5.创建线程的三种方式的对比?
a、采用实现 Runnable、Callable 接口的方式创建多线程。
优势是:
线程类只是实现了 Runnable 接口或 Callable 接口,还可以继承其他类。
在这种方式下,多个线程可以共享同一个 target 对象,所以非常适合多个相同线程来处理同一份资源的情况,从而可以将 CPU、代码和数据分开,形成清晰的模型,较好地体现了面向对象的思想。
劣势是:
编程稍微复杂,如果要访问当前线程,则必须使用 Thread.currentThread()方法。

b、使用继承 Thread 类的方式创建多线程
优势是:
编写简单,如果需要访问当前线程,则无需使用 Thread.currentThread()方法,直接使用 this 即可获得当前线程。
劣势是:
线程类已经继承了 Thread 类,所以不能再继承其他父类。

c、Runnable 和 Callable 的区别
1、Callable 规定(重写)的方法是 call(),Runnable 规定(重写)的方法是 run()。
2、Callable 的任务执行后可返回值,而 Runnable 的任务是不能返回值的。
3、Call 方法可以抛出异常,Runnable 方法不可以。
4、运行 Callable 任务可以拿到一个 Future 对象,表示异步计算的结果。它提供了检查计算是否完成的方法,以等待计算的完成,并检索计算的结果。通过 Future对象可以了解任务执行情况,可取消任务的执行,还可获取执行结果。

7.为什么使用 Executor 框架?
每次执行任务创建线程 new Thread()比较消耗性能,创建一个线程是比较耗时、耗资源的。
调用 new Thread()创建的线程缺乏管理,被称为野线程,而且可以无限制的创建,线程之间的相互竞争会导致过多占用系统资源而导致系统瘫痪,还有线程之间的频繁交替也会消耗很多系统资源。
接使用 new Thread() 启动的线程不利于扩展,比如定时执行、定期执行、定时定期执行、线程中断等都不便实现。

8、在 Java 中 Executor 和 Executors 的区别?
Executors 工具类的不同方法按照我们的需求创建了不同的线程池,来满足业务的需求。
Executor 接口对象能执行我们的线程任务。
ExecutorService 接口继承了 Executor 接口并进行了扩展,提供了更多的方法我们能获得任务执行的状态并且可以获取任务的返回值。
使用 ThreadPoolExecutor 可以创建自定义线程池。
Future 表示异步计算的结果,他提供了检查计算是否完成的方法,以等待计算的完成,并可以使用 get()方法获取计算的结果。

9.什么是 Callable 和 Future?
Callable 接口类似于 Runnable,从名字就可以看出来了,但是 Runnable 不会返回结果,并且无法抛出返回结果的异常,而 Callable 功能更强大一些,被线程执行后,可以返回值,这个返回值可以被 Future 拿到,也就是说,Future 可以拿到异步执行任务的返回值。
可以认为是带有回调的 Runnable。
Future 接口表示异步任务,是还没有完成的任务给出的未来结果。所以说 Callable用于产生结果,Future 用于获取结果。

总结

1.Java自带的线程池
单个线程的线程池
指定线程个数的线程池
可缓存的线程池
延迟任务的线程池

2.线程池的7大参数
核心线程数
最大线程数
任务队列(有界、无界、同步、优先队列)
拒绝策略
闲置时间
时间单位
线程工厂

3.线程池的调用步骤(核心线程、任务队列、普通线程、拒绝策略)

4.任务队列及底层原理(有界、无界、同步、优先队列)

5.自定义线程池
自定义线程工厂
自定义拒绝策略

6.带有返回值的任务类 --Callable

  • 27
    点赞
  • 17
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

A 北枝

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值