深圳做棋牌网站建设多少钱,京东电子商务网站的建设,花园设计网站推荐,广东企业建网站一、阻塞队列
队列是一种特殊的线性表#xff0c;特殊之处在于它只允许在表的前端#xff08;front#xff09;进行删除操作#xff0c;而在表的后端#xff08;rear#xff09;进行插入操作#xff0c;和栈一样#xff0c;队列是一种操作受限制的线性表。进行插入操作…一、阻塞队列
队列是一种特殊的线性表特殊之处在于它只允许在表的前端front进行删除操作而在表的后端rear进行插入操作和栈一样队列是一种操作受限制的线性表。进行插入操作的端称为队尾进行删除操作的端称为队头。
在队列中插入一个队列元素称为入队从队列中删除一个队列元素称为出队。因为队列只允许在一端插入在另一端删除所以只有最早进入队列的元素才能最先从队列中删除故队列又称为先进先出FIFO—first in first out线性表。 什么是阻塞队列
1支持阻塞的插入方法意思是当队列满时队列会阻塞插入元素的线程直到队列不满。 2支持阻塞的移除方法意思是在队列为空时获取元素的线程会等待队列变为非空。
在并发编程中使用生产者和消费者模式能够解决绝大多数并发问题。该模式通过平衡生产线程和消费线程的工作能力来提高程序整体处理数据的速度。
在线程世界里生产者就是生产数据的线程消费者就是消费数据的线程。在多线程开发中如果生产者处理速度很快而消费者处理速度很慢那么生产者就必须等待消费者处理完才能继续生产数据。同样的道理如果消费者的处理能力大于生产者那么消费者就必须等待生产者。
为了解决这种生产消费能力不均衡的问题便有了生产者和消费者模式。生产者和消费者模式是通过一个容器来解决生产者和消费者的强耦合问题。生产者和消费者彼此之间不直接通信而是通过阻塞队列来进行通信所以生产者生产完数据之后不用等待消费者处理直接扔给阻塞队列消费者不找生产者要数据而是直接从阻塞队列里取阻塞队列就相当于一个缓冲区平衡了生产者和消费者的处理能力。
阻塞队列常用于生产者和消费者的场景生产者是向队列里添加元素的线程消费者是从队列里取元素的线程。阻塞队列就是生产者用来存放元素、消费者用来获取元素的容器。 抛出异常当队列满时如果再往队列里插入元素会抛出IllegalStateExceptionQueuefull异常。当队列空时从队列里获取元素会抛出NoSuchElementException异常。
返回特殊值当往队列插入元素时会返回元素是否插入成功成功返回true。如果是移除方法则是从队列里取出一个元素如果没有则返回null。
一直阻塞当阻塞队列满时如果生产者线程往队列里put元素队列会一直阻塞生产者线程直到队列可用或者响应中断退出。当队列空时如果消费者线程从队列里take元素队列会阻塞住消费者线程直到队列不为空。
超时退出当阻塞队列满时如果生产者线程往队列里插入元素队列会阻塞生产者线程一段时间如果超过了指定的时间生产者线程就会退出。
常用阻塞队列
ArrayBlockingQueue一个由数组结构组成的有界阻塞队列。
LinkedBlockingQueue一个由链表结构组成的有界阻塞队列。
PriorityBlockingQueue一个支持优先级排序的无界阻塞队列。
DelayQueue一个使用优先级队列实现的无界阻塞队列。
SynchronousQueue一个不存储元素的阻塞队列。
LinkedTransferQueue一个由链表结构组成的无界阻塞队列。
LinkedBlockingDeque一个由链表结构组成的双向阻塞队列。
以上的阻塞队列都实现了BlockingQueue接口也都是线程安全的。
有界和无界
有限队列就是长度有限满了以后生产者会阻塞无界队列就是里面能放无数的东西而不会因为队列长度限制被阻塞当然空间限制来源于系统资源的限制如果处理不及时导致队列越来越大越来越大超出一定的限制致使内存超限会导致 OOM 。
无界也会阻塞因为阻塞不仅仅体现在生产者放入元素时会阻塞消费者拿取元素时如果没有元素同样也会阻塞。
ArrayBlockingQueue
是一个用数组实现的有界阻塞队列。此队列按照先进先出FIFO的原则对元素进行排序。默认情况下不保证线程公平的访问队列所谓公平访问队列是指阻塞的线程可以按照阻塞的先后顺序访问队列即先阻塞线程先访问队列。非公平性是对先等待的线程是非公平的当队列可用时阻塞的线程都可以争夺访问队列的资格有可能先阻塞的线程最后才访问队列。初始化时有参数可以设置。
LinkedBlockingQueue
是一个用链表实现的有界阻塞队列。此队列的默认和最大长度为Integer.MAX_VALUE。此队列按照先进先出的原则对元素进行排序。
Array实现和Linked实现的区别
1. 队列中锁的实现不同
ArrayBlockingQueue实现的队列中的锁是没有分离的即生产和消费用的是同一个锁 LinkedBlockingQueue实现的队列中的锁是分离的即生产用的是putLock消费是takeLock。
2. 在生产或消费时操作不同
ArrayBlockingQueue实现的队列中在生产和消费的时候是直接将枚举对象插入或移除的 LinkedBlockingQueue实现的队列中在生产和消费的时候需要把枚举对象转换为NodeE进行插入或移除会影响性能。
3. 队列大小初始化方式不同
ArrayBlockingQueue实现的队列中必须指定队列的大小 LinkedBlockingQueue实现的队列中可以不指定队列的大小但是默认是Integer.MAX_VALUE。
PriorityBlockingQueue
PriorityBlockingQueue是一个支持优先级的无界阻塞队列。默认情况下元素采取自然顺序升序排列。也可以自定义类实现compareTo()方法来指定元素排序规则或者初始化PriorityBlockingQueue时指定构造参数Comparator来对元素进行排序。需要注意的是不能保证同优先级元素的顺序。
DelayQueue
是一个支持延时获取元素的无界阻塞队列。队列使用PriorityQueue来实现。队列中的元素必须实现Delayed接口在创建元素时可以指定多久才能从队列中获取当前元素。只有在延迟期满时才能从队列中提取元素。
DelayQueue运用在以下应用场景
缓存系统的设计可以用DelayQueue保存缓存元素的有效期使用一个线程循环查询DelayQueue一旦能从DelayQueue中获取元素时表示缓存有效期到了。
SynchronousQueue
是一个不存储元素的阻塞队列。每一个put操作必须等待一个take操作否则不能继续添加元素。SynchronousQueue可以看成是一个传球手负责把生产者线程处理的数据直接传递给消费者线程。队列本身并不存储任何元素非常适合传递性场景。SynchronousQueue的吞吐量高于LinkedBlockingQueue和ArrayBlockingQueue。
二、线程池
为什么要用线程池
Java中的线程池是运用场景最多的并发框架几乎所有需要异步或并发执行任务的程序都可以使用线程池。在开发过程中合理地使用线程池能够带来3个好处。
第一降低资源消耗。通过重复利用已创建的线程降低线程创建和销毁造成的消耗。
第二提高响应速度。当任务到达时任务可以不需要等到线程创建就能立即执行。假设一个服务器完成一项任务所需时间为T1 创建线程时间T2 在线程中执行任务的时间T3 销毁线程时间。 如果T1 T3 远大于 T2则可以采用线程池以提高服务器性能。线程池技术正是关注如何缩短或调整T1,T3时间的技术从而提高服务器程序性能的。它把T1T3分别安排在服务器程序的启动和结束的时间段或者一些空闲的时间段这样在服务器程序处理客户请求时不会有T1T3的开销了。
第三提高线程的可管理性。线程是稀缺资源如果无限制地创建不仅会消耗系统资源还会降低系统的稳定性使用线程池可以进行统一分配、调优和监控。 ThreadPoolExecutor 的类关系
Executor是一个接口它是Executor框架的基础它将任务的提交与任务的执行分离开来。
ExecutorService接口继承了Executor在其上做了一些shutdown()、submit()的扩展可以说是真正的线程池接口
AbstractExecutorService抽象类实现了ExecutorService接口中的大部分方法
ThreadPoolExecutor是线程池的核心实现类用来执行被提交的任务。
ScheduledExecutorService接口继承了ExecutorService接口提供了带周期执行功能ExecutorService
ScheduledThreadPoolExecutor是一个实现类可以在给定的延迟后运行命令或者定期执行命令。ScheduledThreadPoolExecutor比Timer更灵活功能更强大。
线程池的创建各个参数含义
public ThreadPoolExecutor(int corePoolSize,int maximumPoolSize,long keepAliveTime,TimeUnit unit,BlockingQueueRunnable workQueue,ThreadFactory threadFactory,RejectedExecutionHandler handler)
corePoolSize
线程池中的核心线程数当提交一个任务时线程池创建一个新线程执行任务直到当前线程数等于corePoolSize如果当前线程数为corePoolSize继续提交的任务被保存到阻塞队列中等待被执行如果执行了线程池的prestartAllCoreThreads()方法线程池会提前创建并启动所有核心线程。
maximumPoolSize
线程池中允许的最大线程数。如果当前阻塞队列满了且继续提交任务则创建新的线程执行任务前提是当前线程数小于maximumPoolSize。
keepAliveTime
线程空闲时的存活时间即当线程没有任务执行时继续存活的时间。默认情况下该参数只在线程数大于corePoolSize时才有用。
TimeUnit
keepAliveTime的时间单位。
workQueue
workQueue必须是BlockingQueue阻塞队列。当线程池中的线程数超过它的corePoolSize的时候线程会进入阻塞队列进行阻塞等待。通过workQueue线程池实现了阻塞功能。
一般来说我们应该尽量使用有界队列因为使用无界队列作为工作队列会对线程池带来如下影响
1当线程池中的线程数达到corePoolSize后新任务将在无界队列中等待因此线程池中的线程数不会超过corePoolSize。 2由于1使用无界队列时maximumPoolSize将是一个无效参数。 3由于1和2使用无界队列时keepAliveTime将是一个无效参数。 4更重要的使用无界queue可能会耗尽系统资源有界队列则有助于防止资源耗尽同时即使使用有界队列也要尽量控制队列的大小在一个合适的范围。
threadFactory
创建线程的工厂通过自定义的线程工厂可以给每个新建的线程设置一个具有识别度的线程名当然还可以更加自由的对线程做更多的设置比如设置所有的线程为守护线程。
Executors静态工厂里默认的threadFactory线程的命名规则是“pool-数字-thread-数字”。
RejectedExecutionHandler
线程池的饱和策略当阻塞队列满了且没有空闲的工作线程如果继续提交任务必须采取一种策略处理该任务线程池提供了4种策略
1AbortPolicy直接抛出异常默认策略 2CallerRunsPolicy用调用者所在的线程来执行任务 3DiscardOldestPolicy丢弃阻塞队列中靠最前的任务并执行当前任务 4DiscardPolicy直接丢弃任务
当然也可以根据应用场景实现RejectedExecutionHandler接口自定义饱和策略如记录日志或持久化存储不能处理的任务。
线程池的工作机制
1如果当前运行的线程少于corePoolSize则创建新线程来执行任务注意执行这一步骤需要获取全局锁。 2如果运行的线程等于或多于corePoolSize则将任务加入BlockingQueue。 3如果无法将任务加入BlockingQueue队列已满则创建新的线程来处理任务。 4如果创建新线程将使当前运行的线程超出maximumPoolSize任务将被拒绝并调用RejectedExecutionHandler.rejectedExecution()方法。
提交任务
execute()方法用于提交不需要返回值的任务所以无法判断任务是否被线程池执行成功。
submit()方法用于提交需要返回值的任务。线程池会返回一个future类型的对象通过这个future对象可以判断任务是否执行成功并且可以通过future的get()方法来获取返回值get()方法会阻塞当前线程直到任务完成而使用getlong timeoutTimeUnit unit方法则会阻塞当前线程一段时间后立即返回这时候有可能任务没有执行完。
关闭线程池
可以通过调用线程池的shutdown或shutdownNow方法来关闭线程池。它们的原理是遍历线程池中的工作线程然后逐个调用线程的interrupt方法来中断线程所以无法响应中断的任务可能永远无法终止。但是它们存在一定的区别shutdownNow首先将线程池的状态设置成STOP然后尝试停止所有的正在执行或暂停任务的线程并返回等待执行任务的列表而shutdown只是将线程池的状态设置成SHUTDOWN状态然后中断所有没有正在执行任务的线程。
只要调用了这两个关闭方法中的任意一个isShutdown方法就会返回true。当所有的任务都已关闭后才表示线程池关闭成功这时调用isTerminaed方法会返回true。至于应该调用哪一种方法来关闭线程池应该由提交到线程池的任务特性决定通常调用shutdown方法来关闭线程池如果任务不一定要执行完则可以调用shutdownNow方法。
合理地配置线程池
要想合理地配置线程池就必须首先分析任务特性可以从以下几个角度来分析
1任务的性质CPU密集型任务、IO密集型任务和混合型任务。 2任务的优先级高、中和低。 3任务的执行时间长、中和短。 4任务的依赖性是否依赖其他系统资源如数据库连接。
性质不同的任务可以用不同规模的线程池分开处理。
CPU密集型任务应配置尽可能小的线程如配置Ncpu1个线程的线程池。由于IO密集型任务线程并不是一直在执行任务则应配置尽可能多的线程如2*Ncpu。
混合型的任务如果可以拆分将其拆分成一个CPU密集型任务和一个IO密集型任务只要这两个任务执行的时间相差不是太大那么分解后执行的吞吐量将高于串行执行的吞吐量。如果这两个任务执行时间相差太大则没必要进行分解。可以通过Runtime.getRuntime().availableProcessors()方法获得当前设备的CPU个数。
优先级不同的任务可以使用优先级队列PriorityBlockingQueue来处理。它可以让优先级高的任务先执行。
执行时间不同的任务可以交给不同规模的线程池来处理或者可以使用优先级队列让执行时间短的任务先执行。
建议使用有界队列。有界队列能增加系统的稳定性和预警能力可以根据需要设大一点。
如果当时我们设置成无界队列那么线程池的队列就会越来越多有可能会撑满内存导致整个系统不可用而不只是后台任务出现问题。