线程池开发实例:
用于oss文件上传时开启线程:
实例一、
Appication启动类:

  1. @Bean(name = "Oss2CdnTaskExecutor")
  2. public TaskExecutor Oss2CdnTaskExecutor() {
  3. final ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
  4. // 设置核心线程数
  5. int max = Runtime.getRuntime().availableProcessors() + 1;
  6. executor.setCorePoolSize(max);
  7. // 设置最大线程数
  8. executor.setMaxPoolSize(max+1);
  9. // 设置队列容量
  10. executor.setQueueCapacity(200);
  11. // 设置线程活跃时间(秒)
  12. executor.setKeepAliveSeconds(60);
  13. // 设置默认线程名称
  14. executor.setThreadNamePrefix("Oss2CdnTaskExecutor-pool-");
  15. // 设置拒绝策略,该任务被线程池拒绝,由调用 execute方法的线程执行该任务
  16. executor.setRejectedExecutionHandler(new ThreadPoolExecutor.DiscardPolicy());
  17. // 等待所有任务结束后再关闭线程池
  18. executor.setWaitForTasksToCompleteOnShutdown(true);
  19. return executor;
  20. }

实体层:

  1. @Override
  2. @Async("Oss2CdnTaskExecutor")//异步调用注解
  3. public String generateCdnUrlAsync(String ossUrl) {
  4. return generateCdnUrl(ossUrl);
  5. }

实例二、

  1. public class ExecutorUtils {
  2. /** 核心线程数,线程闲置的时候也不会收回,当线程数小于核心线程数时,即使有线程空闲,线程池也会优先创建新线程处理*/
  3. private static final int CORE_POOL_SIZE = 50;
  4. /**最大线数,当线程数>=corePoolSize,且任务队列已满时。线程池会创建新线程来处理任务*/
  5. private static final int MAX_POOL_SIZE = 100;
  6. /**当线程空闲时间达到keepAliveTime时,线程会退出,直到线程数量=corePoolSize*/
  7. private static final Long KEEP_ALIVE_TIME = 0L;
  8. /** 当核心线程数达到最大时,新任务会放在队列中排队等待执行*/
  9. private static final int QUEUE_LENGTH = 30000;
  10. public static ThreadPoolExecutor SCHEDULED_THREAD_POOL = new ThreadPoolExecutor(CORE_POOL_SIZE, MAX_POOL_SIZE, KEEP_ALIVE_TIME, TimeUnit.MILLISECONDS,
  11. new LinkedBlockingQueue<Runnable>(QUEUE_LENGTH),
  12. new ThreadFactory(){
  13. @Override
  14. public Thread newThread(Runnable r) {
  15. return new Thread(r, "cloud_thread_getToken_" + r.hashCode());
  16. }},
  17. new ThreadPoolExecutor.AbortPolicy());
  18. public static ExecutorService getThreadPool() {
  19. return SCHEDULED_THREAD_POOL;//返回值是上面new的线程变量对象
  20. }
  21. }

在SpringBoot中使用异步调用是很简单的,只需要使用@Async注解即可实现方法的异步调用。
注意:需要在启动类或配置类加入@EnableAsync使异步调用@Async注解生效。

1、什么是线程池

java.util.concurrent.Executors提供了一个 java.util.concurrent.Executor接口的实现用于创建线程池

一个线程池包括以下四个基本组成部分:
1、线程池管理器(ThreadPool):用于创建并管理线程池,包括 创建线程池,销毁线程池,添加新任务;
2、工作线程(PoolWorker):线程池中线程,在没有任务时处于等待状态,可以循环的执行任务;
3、任务接口(Task):每个任务必须实现的接口,以供工作线程调度任务的执行,它主要规定了任务的入口,任务执行完后的收尾工作,任务的执行状态等;
4、任务队列((askQueue):用于存放没有处理的任务。提供一种缓冲机制。

2.线程池的优势:

  1. 减少了创建和销毁线程的次数,每个工作线程都可以被重复利用,可执行多个任务。
  2. 可以根据系统的承受能力,调整线程池中工作线线程的数目,防止因为消耗过多的内存,而把服务器累趴下(每个线程需要大约1MB内存,线程开的越多,消耗的内存也就越大,最后死机)。
  3. Java里面线程池的顶级接口是Executor,但是严格意义上讲Executor并不是一个线程池,而只是一个执行线程的工具。真正的线程池接口是ExecutorService。
    new Thread 缺点
    1. 每次new Thread新建对象性能差。
    2. 线程缺乏统一管理,可能无限制新建线程,相互之间竞争,及可能占用过多系统资源导致死机或oom。
    3. 缺乏更多功能,如定时执行、定期执行、线程中断。

3、Executors提供四种线程池

newCachedThreadPool创建一个可缓存线程池,如果线程池长度超过处理需要,可灵活回收空闲线程,若无可回收,则新建线程。

newFixedThreadPool 创建一个定长线程池,可控制线程最大并发数,超出的线程会在队列中等待。

newScheduledThreadPool 创建一个定长线程池,支持定时及周期性任务执行。

newSingleThreadExecutor 创建一个单线程化的线程池,它只会用唯一的工作线程来执行任务,保证所有任务按照指定顺序(FIFO, LIFO, 优先级)执行。

一般都不用Executors提供的线程创建方式,使用ThreadPoolExecutor创建线程池

4.不建议使用excutros

规避资源耗尽的风险

说明:定义好的线程池创建方式

Executors返回的线程池对象的弊端如下:

1:FixedThreadPool 和 SingleThreadPool:
允许的请求队列(底层实现是LinkedBlockingQueue)长度为Integer.MAX_VALUE,可能会堆积大量的请求,从而导致OOM
2:CachedThreadPool 和 ScheduledThreadPool
允许的创建线程数量为Integer.MAX_VALUE,可能会创建大量的线程,从而导致OOM

创建线程池的正确姿势

避免使用Executors创建线程池,主要是避免使用其中的默认实现

那么我们可以自己直接调用ThreadPoolExecutor的构造函数来自己创建线程池。在创建的同时,给BlockQueue指定容量就可以了。或者是使用开源类库:

  1. private static ExecutorService executor = new ThreadPoolExecutor(10, 10,
  2. 60L, TimeUnit.SECONDS,
  3. new ArrayBlockingQueue(10));

6、Java线程池七个参数详解

1.corePoolSize 线程池核心线程大小:

(1)在创建了线程池后,当有请求任务来之后,就会安排池中的线程去执行请求任务,理解为今日当值线程。
(2)当线程池中的线程数目达到corePoolSize后,就会把到达的任务放入到缓存队列当中。

  1. 线程池中会维护一个最小的线程数量,即使这些线程处理空闲状态,他们也不会被销毁,除非设置了allowCoreThreadTimeOut。这里的最小线程数量即是corePoolSize<br />**2.maximumPoolSize 线程池最大线程数量**<br /> 一个任务被提交到线程池以后,首先会找有没有空闲存活线程,如果有则直接将任务交给这个空闲线程来执行,如果没有则会缓存到工作队列(后面会介绍)中,如果工作队列满了,才会创建一个新线程,然后从工作队列的头部取出一个任务交由新线程来处理,而将刚提交的任务放入工作队列尾部。线程池不会无限制的去创建新线程,会执行拒绝策略,它会有一个最大线程数量的限制,这个数量即由maximunPoolSize指定。<br />**3.keepAliveTime 空闲线程存活时间**<br /> 一个线程如果处于空闲状态,并且当前的线程数量大于corePoolSize,那么在指定时间后,这个空闲线程会被销毁,直到线程中的线程数不大于corepoolSIze,这里的指定时间由keepAliveTime来设定

4.unit 空闲线程存活时间单位
keepAliveTime的计量单位
5、workQueue 工作队列
新任务被提交后,会先进入到此工作队列中,任务调度时再从队列中取出任务。jdk中提供了四种工作队列:

①ArrayBlockingQueue

基于数组的有界阻塞队列,。如果队列已经是满的,则创建一个新线程,如果线程数量已经达到maxPoolSize,则会执行拒绝策略。

②LinkedBlockingQuene

基于链表的无界阻塞队列,当线程池中线程数量达到核心线程池数量后,再有新任务进来,会一直存入该队列,而不会去创建新线程直到maxPoolSize,因此使用该工作队列时,参数maxPoolSize其实是不起作用的。

③SynchronousQuene

一个不缓存任务的阻塞队列,生产者放入一个任务必须等到消费者取出这个任务。也就是说新任务进来时,不会缓存,而是直接被调度执行该任务,如果没有可用线程,则创建新线程,如果线程数量达到maxPoolSize,则执行拒绝策略。

④PriorityBlockingQueue

具有优先级的无界阻塞队列,优先级通过参数Comparator实现。
六、threadFactory 线程工厂

创建一个新线程时使用的工厂,可以用来设定线程名、是否为daemon线程等等

七、handler 拒绝策略

当工作队列中的任务已到达最大限制,并且线程池中的线程数量也达到最大限制,这时如果有新任务提交进来,该如何处理呢。这里的拒绝策略,就是解决这个问题的,jdk中提供了4中拒绝策略:

①CallerRunsPolicy

该策略下,在调用者线程中直接执行被拒绝任务的run方法,除非线程池已经shutdown,则直接抛弃任务。

②AbortPolicy

为线程池默认的拒绝策略,直接丢弃任务,并抛出RejectedExecutionException异常。
③DiscardPolicy

该策略下,直接丢弃任务,什么都不做=直接抛弃不处理。
④DiscardOldestPolicy

该策略下,抛弃进入队列最早的那个任务,然后尝试把这次拒绝的任务放入队列

7.线程池流程分析

  1. 线程池中线程数**小于corePoolSize**时,**新任务将创建**一个新线程执行任务,不论此时线程池中存在空闲线程;<br /> 线程池中线程数**达到corePoolSize**时,**新任务将被放入workQueue中**,等待线程池中任务调度执行;<br />当workQueue已满,且maximumPoolSize>corePoolSize时,新任务会创建新线程执行任务;<br />当workQueue已满,且提交任务数超过maximumPoolSize,任务由RejectedExecutionHandler处理;<br /> 当线程池中线程数**超过corePoolSize**,且超过这部分的空闲时间达到keepAliveTime时,**回收该线程**;<br /> 如果设置allowCoreThreadTimeOut(true)时,线程池中corePoolSize范围内的线程空闲时间达到keepAliveTime也将回收;

8、线程池的五种状态

running,showdown,stop,Tiding,Terminated

runing:
线程池处于运行状态,可以接受任务,执行任务,创建线程默认就是这个状态。
showDown
调用showdown()函数,不会接受新任务,但是会慢慢处理完堆积的任务。
Stop
调用showdownnow()函数,不会接受新任务,不处理已有任务,会中断现有任务。
Tiding
当线程池状态为showdown或者stop,任务数量为0,就会变为tidying。这个时候会调用钩子函数terminated()。

Terminated:
termiinated()执行完成

9.线程和进程的差别

  1. **进程**是程序的一次执行,
  2. 线程可以理解为进程中执行的一段程序片段
  3. **进程**是独立的,进程是无法突破进程边界存取其他进程内的存储空间,
  4. 线程由于处于进程空间内,所以同一进程所产生的线程共享同一内存空间,线程是属于进程的,当进程退出时,该进程所产生的线程都会被强制退出并清除

10、创建线程的三种方式

第一种方法: 继承Thread类,重写run()方法,run()方法代表线程要执行的任务。
第二种方法: 实现Runnable接口,重写run()方法,run()方法代表线程要执行的任务。
第三种方法: 实现callable接口,重写call()方法,call()作为线程的执行体,具有返回值,并且可以对异常进行声明和抛出
返回值是创建的新线程,new前面的线程对象

  1. final ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
  2. public static ThreadPoolExecutor SCHEDULED_THREAD_POOL = new ThreadPoolExecutor(CORE_POOL_SIZE, MAX_POOL_SIZE, KEEP_ALIVE_TIME, TimeUnit.MILLISECONDS,
  3. new LinkedBlockingQueue<Runnable>(QUEUE_LENGTH)

优缺点:
使用继承的方式编写代码简单,适用于单继承的情况。
使用实现接口的方式避免单继承的局限性,实现资源共享。
推荐使用实现Runnable的方式。

在线程池提交Callable任务后返回了一个Future对象,使用它可以知道Callable任务的状态和得到Callable返回的执行结果。Future提供了get()方法,等待Callable结束并获取它的执行结果。

11. 线程的状态

创建 new线程对象

就绪 调用start方法

运行 run

阻塞 sleep join

死亡 运行完毕

11.run和star的区别

系统调用start()方法启动一个线程,该线程处于就绪状态,而非运行状态,也就意味着这个线程可以被JVM来调度执行,在调度过程中,JVM通过线程类的run()方法来完成实际的操作,当run()方法结束后就会终止;

如果直接调用run()方法,这就相当于一个普通函数的调用,是同步的,无法达到多线程的目的,start()方法能后异步的调用run()方法,真正达到多线程的目的

11、简述sleep方法和wait方法的功能和区别

sleep是让线程休眠一段时间
wait是让线程挂起

12.什么是线程安全和线程不安全?

通俗的说:加锁的就是是线程安全的,不加锁的就是是线程不安全的

线程安全: 就是多线程访问时,采用了加锁机制,当一个线程访问该类的某个数据时,进行保护,其他线程不能进行访问,直到该线程读取完,其他线程才可使用。不会出现数据不一致或者数据污染

线程不安全:就是不提供数据访问保护,有可能出现多个线程先后更改数据造成所得到的数据是脏数据

如果你的代码所在的进程中有多个线程在同时运行,而这些线程可能会同时运行这段代码。如果每次运行结果和单线程运行的结果是一样的,而且其他的变量的值也和预期的是一样的,就是线程安全的。

线程安全问题都是由全局变量及静态变量引起的。若每个线程中对全局变量、静态变量只有读操作,而无写操作,一般来说,这个全局变量是线程安全的;若有多个线程同时执行写操作,一般都需要考虑线程同步,否则的话就可能影响线程安全。

13、什么是自旋锁?

自旋锁是SMP架构中的一种low-level的同步机制

当线程A想要获取一把自旋锁而该锁又被其它线程锁持有时,线程A会在一个循环中自旋以检测锁是不是已经可用了。

锁需要注意

  • 由于自旋时不释放CPU,因而持有自旋锁的线程应该尽快释放自旋锁,否则等待该自旋锁的线程会一直在那里自旋,这就会浪费CPU时间。
  • 持有自旋锁的线程在sleep之前应该释放自旋锁以便其它线程可以获得自旋锁

15、阻塞队列

JDK7提供了7个阻塞队列。(也属于并发容器)

  1. ArrayBlockingQueue :一个由数组结构组成的有界阻塞队列。
  2. LinkedBlockingQueue :一个由链表结构组成的有界阻塞队列。
  3. PriorityBlockingQueue :一个支持优先级排序的无界阻塞队列。
  4. DelayQueue:一个使用优先级队列实现的无界阻塞队列。
  5. SynchronousQueue:一个不存储元素的阻塞队列。
  6. LinkedTransferQueue:一个由链表结构组成的无界阻塞队列。
  7. LinkedBlockingDeque:一个由链表结构组成的双向阻塞队列。
  8. 阻塞队列常用于生产者和消费者的场景,生产者是向队列里添加元素的线程,消费者是从队列里取元素的线程。简而言之,阻塞队列是生产者用来存放元素、消费者获取元素的容器。

16.threadlocal

  1. **ThreadLoca**l叫做**_线程变量_**,意思是ThreadLocal中填充的变量属于当前线程,该变量对其他线程而言是隔离的,也就是说该变量是当前线程独有的变量。ThreadLocal为变量在每个线程中都创建了一个副本,那么每个线程可以访问自己内部的副本变量。
  2. **ThreadLocal 与普通变量的区别在于**,每个使用该变量的线程都会初始化一个完全独立的实例副本。 ThreadLocal 变量通常被private static修饰。当一个线程结束时,它所使用的所有 ThreadLocal 相对的实例副本都可被回收。

17、ThreadLocal与Synchronized

1、Synchronized用于线程间的数据共享

  1. ThreadLocal则用于线程间的数据隔离。

2、Synchronized是利用锁的机制,使变量或代码块在某一时该只能被一个线程访问

3.ThreadLocal为每一个线程都提供了变量的副本,使得每个线程在某一时间访问到的并不是同一个对象,这样就隔离了多个线程对数据的数据共享。而Synchronized却正好相反,它用于在多个线程间通信时能够获得数据共享。

一句话理解ThreadLocal,向ThreadLocal里面存东西就是向它里面的Map存东西的,然后ThreadLocal把这个Map挂到当前的线程底下,这样Map就只属于这个线程了。
三、ThreadLocal的简单使用

  1. public class ThreadLocaDemo {
  2. private static ThreadLocal<String> localVar = new ThreadLocal<String>();
  3. static void print(String str) {
  4. //打印当前线程中本地内存中本地变量的值
  5. System.out.println(str + " :" + localVar.get());
  6. //清除本地内存中的本地变量
  7. localVar.remove();
  8. }
  9. public static void main(String[] args) throws InterruptedException {
  10. new Thread(new Runnable() {
  11. public void run() {
  12. ThreadLocaDemo.localVar.set("local_A");
  13. print("A");
  14. //打印本地变量
  15. System.out.println("after remove : " + localVar.get());
  16. }
  17. },"A").start();
  18. Thread.sleep(1000);
  19. new Thread(new Runnable() {
  20. public void run() {
  21. ThreadLocaDemo.localVar.set("local_B");
  22. print("B");
  23. System.out.println("after remove : " + localVar.get());
  24. }
  25. },"B").start();
  26. }
  27. }
  28. A :local_A
  29. after remove : null
  30. B :local_B
  31. after remove : null

18、ThreadLocal 常见使用场景

  • 1、每个线程需要有自己单独的实例
  • 2、实例需要在多个方法中共享,但不希望被多线程共享

场景

1)存储用户Session

一个简单的用ThreadLocal来存储Session的例子:

  1. private static final ThreadLocal threadSession = new ThreadLocal();
  2. public static Session getSession() throws InfrastructureException {
  3. Session s = (Session) threadSession.get();
  4. try {
  5. if (s == null) {
  6. s = getSessionFactory().openSession();
  7. threadSession.set(s);
  8. }
  9. } catch (HibernateException ex) {
  10. throw new InfrastructureException(ex);
  11. }
  12. return s;

场景二、数据库连接,处理数据库事务

场景三、数据跨层传递(controller,service, dao)

每个线程内需要保存类似于全局变量的信息(例如在拦截器中获取的用户信息),可以让不同方法直接使用,避免参数传递的麻烦却不想被多线程共享(因为不同线程获取到的用户信息不一样)。

例如,用 ThreadLocal 保存一些业务内容(用户权限信息、从用户系统获取到的用户名、用户ID 等),这些信息在同一个线程内相同,但是不同的线程使用的业务内容是不相同的。

在线程生命周期内,都通过这个静态 ThreadLocal 实例的 get() 方法取得自己 set 过的那个对象,避免了将这个对象(如 user 对象)作为参数传递的麻烦。


场景四、Spring使用ThreadLocal解决线程安全问题

19、volatile

volatile是Java提供的一种轻量级的同步机制。Java 语言包含两种内在的同步机制:同步块(或方法)和 volatile 变量,相比于synchronized(synchronized通常称为重量级锁),volatile更轻量级,因为它不会引起线程上下文的切换和调度。但是volatile 变量的同步性较差(有时它更简单并且开销更低),而且其使用也更容易出错。
[

](https://blog.csdn.net/u012723673/article/details/80682208)