线程池

为什么需要线程池

  • 在实际使用中,线程非常的占用资源,对其管理不善很容易造成系统问题。因此在大多数并发框架中,都会使用线程池来管理线程

好处

  1. 使用线程池可以重复利用已有的线程继续执行任务,避免线程在创建和销毁时造成的消耗
  2. 可以提高系统响应速度
  3. 可以对线程进行合理的管理,根据系统的承受能力调节可运行的线程数量的大小

工作原理

线程池的分类

ThreadPoolExecutor

CacheThreadPool

创建一个可根据需要创建新线程的线程池,但是在以前构造的线程可用时将重用它们,并在需要时使用提供的 ThreadFactory 创建新线程

特征

  1. 线程池中数量没有固定,可达到最大值(Interger. MAX_VALUE)
  2. 线程池中的线程可进行缓存重复利用和回收(回收默认时间为1分钟)
  3. 当线程池中,没有可用线程,会重新创建一个线程

举例

public class Task implements Runnable{@Overridepublic void run() {System.out.println(Thread.currentThread().getName()+"running");}
}
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;public class CacheThreadPoolDemo {public static void main(String[] args) {//生成线程池ExecutorService executorService = Executors.newCachedThreadPool();//提交线程池任务for(int i=0;i<20;i++){executorService.execute(new Task());}executorService.shutdown();}
}

FixedThreadPool

创建一个可重用固定线程数的线程池,以共享的无界队列方式来运行这些线程。在任意点,在大多数 nThreads 线程会处于处理任务的活动状态。如果在所有线程处于活动状态时提交附加任务,则在有可用线程之前,附加任务将在队列中等待。如果在关闭前的执行期间由于失败而导致任何线程终止,那么一个新线程将代替它执行后续的任务(如果需要)。在某个线程被显式地关闭之前,池中的线程将一直存在。

特征

  1. 线程池中的线程处于一定的量,可以很好的控制线程的并发量
  2. 线程可以重复被使用,在显示关闭之前,都将一直存在
  3. 超出一定量的线程被提交时候需在队列中等待

举例

public class Task implements Runnable{@Overridepublic void run() {System.out.println(Thread.currentThread().getName()+"running");}
}
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;public class FixedThreadPoolDemo {public static void main(String[] args) {//允许创建的线程最多只有5个ExecutorService executorService = Executors.newFixedThreadPool(5);for(int i=0;i<20;i++){executorService.execute(new Task());}executorService.shutdown();}
}

SingleThreadExecutor

创建一个使用单个 worker 线程的 Executor,以无界队列方式来运行该线程。(注意,如果因为在关闭前的执行期间出现失败而终止了此单个线程,那么如果需要,一个新线程将代替它执行后续的任务)。可保证顺序地执行各个任务,并且在任意给定的时间不会有多个线程是活动的。与其他等效的 newFixedThreadPool(1) 不同,可保证无需重新配置此方法所返回的执行程序即可使用其他的线程。

特征

  • 线程池中最多执行1个线程,之后提交的线程活动将会排在队列中以此执行

举例

public class Task implements Runnable{@Overridepublic void run() {System.out.println(Thread.currentThread().getName()+"running");}
}
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;public class SingleThreadPoolDemo {public static void main(String[] args) {ExecutorService executorService = Executors.newSingleThreadExecutor();for(int i=0;i<20;i++){executorService.execute(new Task());}executorService.shutdown();}
}

ScheduledThreadPoolExecutor

newSingleThreadScheduledExecutor

创建一个单线程执行程序,它可安排在给定延迟后运行命令或者定期地执行。

特征

  1. 线程池中最多执行1个线程,之后提交的线程活动将会排在队列中以此执行
  2. 可定时或者延迟执行线程活动

newScheduledThreadPool

创建一个线程池,它可安排在给定延迟后运行命令或者定期地执行。

特征

  1. 线程池中具有指定数量的线程,即便是空线程也将保留
  2. 可定时或者延迟执行线程活动

举例

例一

import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;public class ScheduledThreadPoolDemo {public static void main(String[] args) {ScheduledExecutorService scheduledExecutorService = Executors.newScheduledThreadPool(3);System.out.println(System.currentTimeMillis());//输出时间scheduledExecutorService.schedule(new Runnable() {@Overridepublic void run() {System.out.println("延迟三秒执行");System.out.println(System.currentTimeMillis());//输出时间}}, 3, TimeUnit.SECONDS);scheduledExecutorService.shutdown();}
}

例二

import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;public class ScheduledThreadPoolDemo {public static void main(String[] args) {ScheduledExecutorService scheduledExecutorService = Executors.newScheduledThreadPool(3);System.out.println(System.currentTimeMillis());scheduledExecutorService.scheduleAtFixedRate(new Runnable() {@Overridepublic void run() {System.out.println("延迟一秒执行,每三秒执行一次");System.out.println(System.currentTimeMillis());}}, 1,3, TimeUnit.SECONDS);}
}

ForkJoinPool

newWorkStealingPool

创建一个带并行级别的线程池,并行级别决定了同一时刻最多有多少个线程在执行,如不传如并行级别参数,将默认为当前系统的CPU个数

举例

package forkjoin;import java.util.concurrent.RecursiveAction;class PrintTask extends RecursiveAction {private static final int THRESHOLD = 50; //最多只能打印50个数private int start;private int end;public PrintTask(int start, int end) {super();this.start = start;this.end = end;}@Overrideprotected void compute() {if(end - start < THRESHOLD){for(int i=start;i<end;i++){System.out.println(Thread.currentThread().getName()+"的i值:"+i);}}else {int middle =(start+end)/2;PrintTask left = new PrintTask(start, middle);PrintTask right = new PrintTask(middle, end);//并行执行两个“小任务”left.fork();right.fork();}}}
package forkjoin;import java.util.concurrent.ForkJoinPool;
import java.util.concurrent.TimeUnit;/***分而治之的思想* 简单的打印0-300的数值。用多线程实现并行执行**/
public class ForkJoinPoolAction {public static void main(String[] args) throws Exception{PrintTask task = new PrintTask(0, 300);//创建实例,并执行分割任务ForkJoinPool pool = new ForkJoinPool();pool.submit(task);//线程阻塞,等待所有任务完成pool.awaitTermination(2, TimeUnit.SECONDS);pool.shutdown();}
}

线程池的生命周期

只有两种

  • RUNNING
  • TERMINATED
    • 但是会有三种过渡状态

      • SHUIDOWN
      • STOP
      • TIDYING

线程池的创建

corePoolSize

  • 核心线程池的个数

maximumPoolSize

  • 线程池能创建线程的最大个数

keepAliveTime

  • 空闲线程存活时间

unit

  • 时间单位,为keepAliveTime指定时间单位

workQueue

  • 阻塞队列,用于保存任务的阻塞队列

threadFactory

  • 创建线程的工程类

handler

  • 饱和策略(拒绝策略)

阻塞队列

ArrayBlockingQueue

​ 基于数组的阻塞队列实现,在ArrayBlockingQueue内部,维护了一个定长数组,以便缓存队列中的数据对象,这是一个常用的阻塞队列,除了一个定长数组外,ArrayBlockingQueue内部还保存着两个整形变量,分别标识着队列的头部和尾部在数组中的位置。

ArrayBlockingQueue在生产者放入数据和消费者获取数据,都是共用同一个锁对象,由此也意味着两者无法真正并行运行,这点尤其不同于LinkedBlockingQueue;按照实现原理来分析,ArrayBlockingQueue完全可以采用分离锁,从而实现生产者和消费者操作的完全并行运行。Doug Lea之所以没这样去做,也许是因为ArrayBlockingQueue的数据写入和获取操作已经足够轻巧,以至于引入独立的锁机制,除了给代码带来额外的复杂性外,其在性能上完全占不到任何便宜。 ArrayBlockingQueue和LinkedBlockingQueue间还有一个明显的不同之处在于,前者在插入或删除元素时不会产生或销毁任何额外的对象实例,而后者则会生成一个额外的Node对象。这在长时间内需要高效并发地处理大批量数据的系统中,其对于GC的影响还是存在一定的区别。而在创建ArrayBlockingQueue时,我们还可以控制对象的内部锁是否采用公平锁,默认采用非公平锁。

举例

1.生产者

package ArrayBlockingQueue;import java.util.concurrent.BlockingQueue;public class Producer implements Runnable {private BlockingQueue<Integer> blockingQueue;private static int element = 0;public Producer(BlockingQueue<Integer> blockingQueue) {this.blockingQueue = blockingQueue;}public void run() {try {while(element < 20) {System.out.println("将要放进去的元素是:"+element);blockingQueue.put(element++);}} catch (Exception e) {System.out.println("生产者在等待空闲空间的时候被打断了!");e.printStackTrace();}System.out.println("生产者已经终止了生产过程!");}
}

2.消费者

package ArrayBlockingQueue;import java.util.concurrent.BlockingQueue;public class Consumer implements Runnable {private BlockingQueue<Integer> blockingQueue;public Consumer(BlockingQueue<Integer> blockingQueue) {this.blockingQueue = blockingQueue;}public void run() {try {while(true) {System.out.println("取出来的元素是:"+blockingQueue.take());}} catch (Exception e) {System.out.println("消费者在等待新产品的时候被打断了!");e.printStackTrace();}}
}

3.测试类

package ArrayBlockingQueue;import java.util.concurrent.ArrayBlockingQueue;
import java.util.concurrent.BlockingQueue;public class MainClass {public static void main(String[] args) {BlockingQueue<Integer> blockingQueue = new ArrayBlockingQueue<Integer>(3,true);Producer producerPut = new Producer(blockingQueue);Consumer consumer = new Consumer(blockingQueue);
//    ProducerOffer producerOffer = new ProducerOffer(blockingQueue);new Thread(producerPut).start();new Thread(consumer).start();}
}

LinkedBlockingQueue

​ 基于链表的阻塞队列,同ArrayListBlockingQueue类似,其内部也维持着一个数据缓冲队列(该队列由一个链表构成),当生产者往队列中放入一个数据时,队列会从生产者手中获取数据,并缓存在队列内部,而生产者立即返回;只有当队列缓冲区达到最大值缓存容量时(LinkedBlockingQueue可以通过构造函数指定该值),才会阻塞生产者队列,直到消费者从队列中消费掉一份数据,生产者线程会被唤醒,反之对于消费者这端的处理也基于同样的原理。而LinkedBlockingQueue之所以能够高效的处理并发数据,还因为其对于生产者端和消费者端分别采用了独立的锁来控制数据同步,这也意味着在高并发的情况下生产者和消费者可以并行地操作队列中的数据,以此来提高整个队列的并发性能。

举例

与ArrayBlockingQueue类似

DelayQueue

DelayQueue中的元素只有当其指定的延迟时间到了,才能够从队列中获取到该元素。DelayQueue是一个没有大小限制的队列,因此往队列中插入数据的操作(生产者)永远不会被阻塞,而只有获取数据的操作(消费者)才会被阻塞。

使用场景:

DelayQueue使用场景较少,但都相当巧妙,常见的例子比如使用一个DelayQueue来管理一个超时未响应的连接队列。

举例

package delayqueue;import java.util.concurrent.DelayQueue;
import java.util.concurrent.Delayed;
import java.util.concurrent.TimeUnit;public class DelayQueueTest {public static void main(String[] args) {DelayQueue<DelayTask> queue = new DelayQueue<>();queue.add(new DelayTask("1", 1000L, TimeUnit.MILLISECONDS));queue.add(new DelayTask("2", 2000L, TimeUnit.MILLISECONDS));queue.add(new DelayTask("3", 3000L, TimeUnit.MILLISECONDS));System.out.println("queue put done");while(!queue.isEmpty()) {try {DelayTask task = queue.take();System.out.println(task.name + ":" + System.currentTimeMillis());} catch (InterruptedException e) {e.printStackTrace();}}}
}
class DelayTask implements Delayed {public String name;public Long delayTime;public TimeUnit delayTimeUnit;public Long executeTime;//msDelayTask(String name, long delayTime, TimeUnit delayTimeUnit) {this.name = name;this.delayTime = delayTime;this.delayTimeUnit = delayTimeUnit;this.executeTime = System.currentTimeMillis() + delayTimeUnit.toMillis(delayTime);}@Overridepublic int compareTo(Delayed o) {if(this.getDelay(TimeUnit.MILLISECONDS) > o.getDelay(TimeUnit.MILLISECONDS)) {return 1;}else if(this.getDelay(TimeUnit.MILLISECONDS) < o.getDelay(TimeUnit.MILLISECONDS)) {return -1;}return 0;}@Overridepublic long getDelay(TimeUnit unit) {return unit.convert(executeTime - System.currentTimeMillis(), TimeUnit.MILLISECONDS);}
}

PriorityBlockingQueue

基于优先级的阻塞队列(优先级的判断通过构造函数传入的Compator对象来决定),但需要注意的是PriorityBlockingQueue并不会阻塞数据生产者,而只会在没有可消费的数据时,阻塞数据的消费者。因此使用的时候要特别注意,生产者生产数据的速度绝对不能快于消费者消费数据的速度,否则时间一长,会最终耗尽所有的可用堆内存空间。在实现PriorityBlockingQueue时,内部控制线程同步的锁采用的是公平锁。

举例

package priorityqueue;public class Task implements Comparable<Task> {private int id;private String name;public int getId() {return id;}public void setId(int id) {this.id = id;}public String getName() {return name;}public void setName(String name) {this.name = name;}@Overridepublic int compareTo(Task task) {return this.id > task.id ? 1 : (this.id < task.id ? -1 : 0);}public String toString() {return this.id + "," + this.name;}}
package priorityqueue;import java.util.concurrent.PriorityBlockingQueue;public class UsePriorityBlockingQueue {public static void main(String[] args) throws Exception{PriorityBlockingQueue<Task> q = new PriorityBlockingQueue<Task>();Task t1 = new Task();t1.setId(3);t1.setName("id为3");Task t2 = new Task();t2.setId(4);t2.setName("id为4");Task t3 = new Task();t3.setId(1);t3.setName("id为1");q.add(t1); //3q.add(t2); //4q.add(t3);  //1System.out.println("容器:" + q);System.out.println(q.take().getId());System.out.println("容器:" + q);}
}

SynchronousQueue

​ 一种无缓冲的等待队列,类似于无中介的直接交易,有点像原始社会中的生产者和消费者,生产者拿着产品去集市销售给产品的最终消费者,而消费者必须亲自去集市找到所要商品的直接生产者,如果一方没有找到合适的目标,那么对不起,大家都在集市等待。相对于有缓冲的BlockingQueue来说,少了一个中间经销商的环节(缓冲区),如果有经销商,生产者直接把产品批发给经销商,而无需在意经销商最终会将这些产品卖给那些消费者,由于经销商可以库存一部分商品,因此相对于直接交易模式,总体来说采用中间经销商的模式会吞吐量高一些(可以批量买卖);但另一方面,又因为经销商的引入,使得产品从生产者到消费者中间增加了额外的交易环节,单个产品的及时响应性能可能会降低。

声明一个SynchronousQueue有两种不同的方式,它们之间有着不太一样的行为。公平模式和非公平模式的区别:

如果采用公平模式:SynchronousQueue会采用公平锁,并配合一个FIFO队列来阻塞多余的生产者和消费者,从而体系整体的公平策略;

但如果是非公平模式(SynchronousQueue默认):SynchronousQueue采用非公平锁,同时配合一个LIFO队列来管理多余的生产者和消费者,而后一种模式,如果生产者和消费者的处理速度有差距,则很容易出现饥渴的情况,即可能有某些生产者或者是消费者的数据永远都得不到处理。

举例

package Synchronousqueue;import java.util.Random;
import java.util.UUID;
import java.util.concurrent.BlockingQueue;
import java.util.concurrent.SynchronousQueue;public class SynchronousQueueExample {static class SynchronousQueueProducer implements Runnable {protected BlockingQueue<String> blockingQueue;final Random random = new Random();public SynchronousQueueProducer(BlockingQueue<String> queue) {this.blockingQueue = queue;}@Overridepublic void run() {while (true) {try {String data = UUID.randomUUID().toString();System.out.println("Put: " + data);blockingQueue.put(data);Thread.sleep(1000);} catch (InterruptedException e) {e.printStackTrace();}}}}static class SynchronousQueueConsumer implements Runnable {protected BlockingQueue<String> blockingQueue;public SynchronousQueueConsumer(BlockingQueue<String> queue) {this.blockingQueue = queue;}@Overridepublic void run() {while (true) {try {String data = blockingQueue.take();System.out.println(Thread.currentThread().getName()+ " take(): " + data);Thread.sleep(2000);} catch (InterruptedException e) {e.printStackTrace();}}}}public static void main(String[] args) {final BlockingQueue<String> synchronousQueue = new SynchronousQueue<String>();SynchronousQueueProducer queueProducer = new SynchronousQueueProducer(synchronousQueue);new Thread(queueProducer).start();SynchronousQueueConsumer queueConsumer1 = new SynchronousQueueConsumer(synchronousQueue);new Thread(queueConsumer1).start();SynchronousQueueConsumer queueConsumer2 = new SynchronousQueueConsumer(synchronousQueue);new Thread(queueConsumer2).start();}
}

ArrayBlockingQueue和LinkedBlockingQueue区别

  • 队列中锁的实现不同

    ArrayBlockingQueue实现的队列中的锁是没有分离的,即生产和消费用的是同一个锁;

    LinkedBlockingQueue实现的队列中的锁是分离的,即生产用的是putLock,消费是takeLock

  • 队列大小初始化方式不同

    ArrayBlockingQueue实现的队列中必须指定队列的大小;

    LinkedBlockingQueue实现的队列中可以不指定队列的大小,但是默认是Integer.MAX_VALUE

拒绝策略

ThreadPoolExecutor.AbortPolicy

  • 丢弃任务并抛出RejectedExecutionException异常。

ThreadPoolExecutor.DiscardPolicy

  • 也是丢弃任务,但是不抛出异常。

ThreadPoolExecutor.DiscardOldestPolicy

  • 丢弃队列最前面的任务,然后重新尝试执行任务(重复此过程)

ThreadPoolExecutor.CallerRunsPolicy

  • 由调用线程处理该任务

execute方法

▪ 如果当前运行的线程少于corePoolSize,则会创建新的线程来执行新的任务;
▪ 如果运行的线程个数等于或者大于corePoolSize,则会将提交的任务存放到阻塞队列workQueue中;
▪ 如果当前workQueue队列已满的话,则会创建新的线程来执行任务;
▪ 如果线程个数已经超过了maximumPoolSize,则会使用饱和策略RejectedExecutionHandler来进行处理。

Executor和Submit

submit是基方法Executor.execute(Runnable)的延伸,通过创建并返回一个Future类对象可用于取消执行和/或等待完成。

import java.util.concurrent.Callable;public class Task1 implements Callable {@Overridepublic Object call() throws Exception {return Thread.currentThread().getName()+"is running";}
}
import java.util.concurrent.ExecutionException;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;public class Test {public static void main(String[] args) {ExecutorService executorService = Executors.newFixedThreadPool(5);for(int i=0;i<10;i++){Future<String> submit = executorService.submit(new Task1());try {String str = submit.get();System.out.println(str);} catch (InterruptedException e) {e.printStackTrace();} catch (ExecutionException e) {e.printStackTrace();}}}
}

线程池的关闭

关闭线程池,可以通过shutdown和shutdownNow两个方法

原理:遍历线程池中的所有线程,然后依次中断

▪ 1、shutdownNow首先将线程池的状态设置为STOP,然后尝试停止所有的正在执行和未执行任务的线程,并返回等待执行任务的列表;

▪ 2、shutdown只是将线程池的状态设置为SHUTDOWN状态,然后中断所有没有正在执行任务的线程

【java学习之路】(java SE篇)011.线程池相关推荐

  1. 个人开发经历--我的java学习之路(学校篇)

    个人开发经历--我的java学习之路(学校篇) 个人介绍: 姓名: 不在这里说明 联系信息: 个人历程 jdbc阶段 sql生成器 一代代码生成器 servlet阶段 servlet项目中,sql生成 ...

  2. java学习之路---java学习的方法和java学习路线

    转载于: http://blog.csdn.net/zhangerqing 前段时间逛论坛,总会有很多新手很迷茫,问到:到底该怎么学好Java,这个问题很重要,尤其对于像我们这样大多数都是靠自学的人来 ...

  3. Java学习之路-----Java基础简介,基础语法,Java标识符,修饰符,关键字与注释

    这里写目录标题 Java简介 Java发展历程 Java语言平台版本 Java语言的特点 Java语言跨平台原理--JVM JRE和JDK JDK安装路径下的目录解释 Java基础语法 Java标识符 ...

  4. Java学习之路 -- Java怎么学?

    文章目录 java基础怎么学? 学完基础学什么? 几个常用框架学完学什么? MQ JVM的知识跑不掉 微服务等等 其他 数据结构和算法 java基础怎么学? 当时,作为懵懂的小白,大一学习了c和c++ ...

  5. JAVA学习之路--基础篇三

    目录 关于Java中从键盘输入的语句 nextxxx().next().nextLine()的区别 语句 if和if else语句 Switch语句 for语句 while和do..while bre ...

  6. java学习之路目录(已完结)

    java学习之路目录(持续更新中-) 第一阶段 javaSE(完结) 序号 标题 内容 001 java初识 java语言特点.体系结构.运行机制 002 java SE基础语法 注释.关键字.变量. ...

  7. 我的Java学习之路2009-11-17

    -------------------------------2009年3月19日开始----------------------------- 下载JDK Myeclipse Netbeans JB ...

  8. 菜鸟haqima的Java学习之路第一天

    菜鸟haqima的Java学习之路第一天 导读:DOS命令 常用快捷键 Java的简单概述 (第一章 Java开发环境的搭建) 1.常用的DOS命令 1.1.怎么打开DOS命令窗口 win键+r(组合 ...

  9. Java学习之路02_选择方向_旺旺老师

    提示:请您先阅读第一部分:Java学习之路01_软件江湖_旺旺老师 第二部分:帮派之争 就好像玩游戏要先选择角色种族,进入江湖要先选择帮派,那搞软件开发也要选择方向.个人总结的软件开发的方向:嵌入式开 ...

  10. Java学习之路-预科

    Java学习之路-预科 第一章 学习计算机的基本知识 文章目录 Java学习之路-预科 前言 一.什么是计算机? 二.硬件及冯诺依曼结构 1.计算机硬件 1.1 计算机硬件组成 1.2 什么是装机 1 ...

最新文章

  1. linux系统启动流程详解
  2. OpenCASCADE:使用 XDE 文档
  3. 由于html元素加载导致的问题
  4. 介绍一个Spring Cloud分布式微服务架构图
  5. python自动化测试-Python自动化测试入门,看这一篇就足以
  6. 计算机网络基础知识笔记
  7. div水平垂直居中方法汇总(共六种)
  8. Wechall Wireup(一)
  9. 七大行星排列图片_八大行星图片欣赏
  10. 图解传说中的HTTP协议
  11. UOS 安装腾讯会议的踩坑记录
  12. Apple Pay正式入华:能否成支付宝与微信强敌
  13. 魔兽世界称全球玩家达1150万
  14. IP、MAC地址,交换机路由器,ARP、NAT协议串讲
  15. RISC V (RV32+RV64) 架构 整体介绍
  16. 2021秋招面试经历----硬件工程师
  17. linux下最好的chm阅读器KchmViewer,安装使用/与oklular,xCHM,gnochm简单比较
  18. 区块链系列----Pos大有可为
  19. [和管子对话] 1 2007-4-5/对面向对象的你言我语 (转载)
  20. 在Windows系统中搭建PHP环境,PHP环境搭建-Windows系统下PHP环境搭建

热门文章

  1. 天梯—计算阶乘和(C语言)
  2. C++ TCP socket 非阻塞连接超时设定方式
  3. C#winform遍历控件判断控件类型
  4. Eigen教程(2)之Matrix,Vectors, Dynamic介绍
  5. static在内存层面的作用_static关键字总结
  6. Numpy的广播功能
  7. CountDownLatch使用解说
  8. 苹果开发者账户需要同意并添加电话号码,苹果账号忘记验证问题解决方案
  9. android 自定义button点击事件,自定义View(2)-从源码分析button的onClick和onTouch
  10. Web 学习之跨域问题及解决方案