多线程相关知识回顾第一篇

2 篇文章 0 订阅
(盗图)
开局一张图,内容全靠编:
这张图是我们JDK中线程池的部分结构,所有类都继承自Executor, Executor 顾名思义是专门用来处理多线程相关的一个接口, 里面有一个execute()方法,用来执行线程
一、线程池接口:ExecutorService为线程池接口,提供了线程池生命周期方法(shutdown(),submit(Runnable task)... ),继承自Executor接口。
ThreadPoolExecutor为线程池实现类,提供了线程池的维护操作等相关方法,继承自AbstractExecutorService,AbstractExecutorService实现了ExecutorService接口。
、JDK线程池默认实现:
FixedThreadPool(具体代码自己看,很简单)
这个线程池的特点:
  • 这是一种线程数量固定的线程池,因为corePoolSize和maximunPoolSize都为用户设定的线程数量nThreads;
  • keepAliveTime为0,意味着一旦有多余的空闲线程,就会被立即停止掉,不过因为最多只有nThreads个线程,且corePoolSize和maximunPoolSize值一致,所以这个值无法发挥作用;
  • 阻塞队列采用了LinkedBlockingQueue,它是一个无界队列,由于阻塞队列是一个无界队列,因此永远不可能拒绝任务。

CachedThreadPool(具体代码自己看,很简单)

    这个线程池的特点
  • 这是一个线程数量可以“无限”扩大(不能超过整型最大值)的线程池;
  • 比较适合处理执行时间比较小的任务;
  • corePoolSize为0,maximumPoolSize为无限大,意味着线程数量可以无限大;
  • keepAliveTime为60S,意味着线程空闲时间超过60S就会被杀死;
  • 采用SynchronousQueue装等待的任务,这个阻塞队列没有存储空间,这意味着只要有请求到来,就必须要找到一条工作线程处理他,如果当前没有空闲的线程,那么就会再创建一条新的线程。

SingleThreadExecutor(具体代码自己看,很简单)

这个线程池的特点:
  • 只有一个线程,使用了无界队列LinkedBlockingQueue,某种意义上等同于newFixedThreadPool(1);
  • 因为只有一个线程,所以能够保证所有任务是FIFO(先进先出)地执行。

ScheduledThreadPool {  在jdk中有Timer 具有类似的功能,只不过Timer是单线程的,具体使用时可以百度,我做的项目中用到了TimerTask 和 ScheduledThreadPool 配合实现接口日志入库,因为TimerTask实现了Runnable,所以可以直接用在线程池中 }

 
  • ScheduledThreadPoolExecutor继承了ThreadPoolExecutor类,因此,整体上功能一致,线程池主要负责创建线程(Worker类),线程从阻塞队列中不断获取新的异步任务,直到阻塞队列中已经没有了异步任务为止。但是相较于ThreadPoolExecutor来说,ScheduledThreadPoolExecutor具有延时执行任务和可周期性执行任务的特性,ScheduledThreadPoolExecutor重新设计了任务类ScheduleFutureTask,ScheduleFutureTask重写了run方法使其具有可延时执行和可周期性执行任务的特性。另外,阻塞队列DelayedWorkQueue是可根据优先级排序的队列,采用了堆的底层数据结构,使得与当前时间相比,待执行时间越靠近的任务放置队头,以便线程能够获取到任务进行执行;
  • 线程池无论是ThreadPoolExecutor还是ScheduledThreadPoolExecutor,在设计时的三个关键要素是:任务,执行者以及任务结果。它们的设计思想也是完全将这三个关键要素进行了解耦。
    执行者
    任务的执行机制,完全交由Worker类,也就是进一步了封装了Thread。向线程池提交任务,无论为ThreadPoolExecutor的execute方法和submit方法,还是ScheduledThreadPoolExecutor的schedule方法,都是先将任务移入到阻塞队列中,然后通过addWork方法新建了Work类,并通过runWorker方法启动线程,并不断的从阻塞对列中获取异步任务执行交给Worker执行,直至阻塞队列中无法取到任务为止。
    任务
    在ThreadPoolExecutor和ScheduledThreadPoolExecutor中任务是指实现了Runnable接口和Callable接口的实现类。ThreadPoolExecutor中会将任务转换成FutureTask类,而在ScheduledThreadPoolExecutor中为了实现可延时执行任务和周期性执行任务的特性,任务会被转换成ScheduledFutureTask类,该类继承了FutureTask,并重写了run方法。
    任务结果
    在ThreadPoolExecutor中提交任务后,获取任务结果可以通过Future接口的类,在ThreadPoolExecutor中实际上为FutureTask类,而在ScheduledThreadPoolExecutor中则是ScheduledFutureTask类
        这里是盗用的别人的结果,具体可以参考( https://www.jianshu.com/p/502f9952c09b
 
以上的线程池我们使用 Executors (线程池工具)来创建:
 
Exectuors工厂实际上就是调用的ExectuorPoolService的构造方法,传入默认参数, 当然,我们也可以直接new ThreadPoolExecutor的构造方法来创建线程池,传入需要的参数。
 
除此之外还有一些小工具例如: countDownLatch, CyclicBarrier、Semaphore、concurrentHashMap和BlockingQueue。它们都是在jdk1.5时被引入的, 存在于java.util.cucurrent包下。
  • countDownLatch这个类使一个线程等待其他线程各自执行完毕后再执行。
  • 是通过一个计数器来实现的,计数器的初始值是线程的数量。每当一个线程执行完毕后,计数器的值就-1,当计数器的值为0时,表示所有线程都执行完毕,然后在闭锁上等待的线程就可以恢复工作了。
//调用await()方法的线程会被挂起,它会等待直到count值为0才继续执行public void await() throws InterruptedException { };
//和await()类似,只不过等待一定的时间后count值还没变为0的话就会继续执行public boolean await(long timeout, TimeUnit unit) throws InterruptedException { };
//将count值减1public void countDown() { };
 
* CountDownLatch和CyclicBarrier区别:
1.countDownLatch是一个计数器,线程完成一个记录一个,计数器递减,只能只用一次
2.CyclicBarrier的计数器更像一个阀门,需要所有线程都到达,然后继续执行,计数器递增,提供reset功能,可以多次使用
更多的内容可以百度。
 
下面说一下Spring 中的线程 ThreadPoolTaskExecutor
ThreadPoolTaskExecutor 的创建方式可以时XML的也可以时bean配置的
 
前边几个参数就不说了,我说一下线程池拒绝策略,Spring 和 jdk 是一致的在这方面:
拒绝策略
 
rejectedExectutionHandler参数字段用于配置绝策略,常用拒绝策略如下
 
AbortPolicy:用于被拒绝任务的处理程序,它将抛出RejectedExecutionException
 
CallerRunsPolicy:用于被拒绝任务的处理程序,它直接在execute方法的调用线程中运行被拒绝的任务。
 
DiscardOldestPolicy:用于被拒绝任务的处理程序,它放弃最旧的未处理请求,然后重试execute。
 
DiscardPolicy:用于被拒绝任务的处理程序,默认情况下它将丢弃被拒绝的任务。
 
处理流程
1.当一个任务被提交到线程池时,首先查看线程池的核心线程是否都在执行任务,否就选择一条线程执行任务,就是执行第二步。
 
2.查看核心线程池是否已满,不满就创建一条线程执行任务,否则执行第三步。
 
3.查看任务队列是否已满,不满就将任务存储在任务队列中,否则执行第四步。
 
4.查看线程池是否已满,不满就创建一条线程执行任务,否则就按照策略处理无法执行的任务。
 
基本流程: 核心线程-> 缓冲队列-> 最大线程 -> 拒绝策略
 
实际上 Spring 为我们提供  @Async注解后,我们使用多线程将变得非常简单
 
一:创建方法,然后在方法上添加@Async注解,然后还需要在@SpringBootApplication启动类或者@configure注解类上 添加注解@EnableAsync启动多线程注解,@Async就会对标注的方法开启异步多线程调用,注意,这个方法的类一定要交给spring容器来管理
 
然后 其他方法就可以直接调用此方法了,spring会开启多线程异步调用。
 
注意:关于注解失效需要注意以下几点
 
     1,注解的方法必须是public方法
 
      2,方法一定要从另一个类中调用,也就是从类的外部调用,类的内部调用是无效的,因为@Transactional和@Async注解的实现都是基于Spring的AOP,而AOP的实现是基于动态代理模式实现的。那么注解失效的原因就很明显了,有可能因为调用方法的是对象本身而不是代理对象,因为没有经过Spring容器。
 
   3,异步方法使用注解@Async的返回值只能为void或者Future
 
 
 
 
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值