美文网首页
JAVA高并发(四)

JAVA高并发(四)

作者: 花开此时海 | 来源:发表于2021-01-30 19:51 被阅读0次

    线程间的协作:

    多线程的世界线程并不孤立,线程间常见的协作方式以及java提供的支持:

        最为经典的便是Object.wait和Object.notify这组方法了,

        wait方法:会使当前线程进入waiting状态,并直到有其它线程调用notify方法或者notifyAll方法进行唤醒,当然也可以加入超时机制,默认就等于wait(0)

        notify方法:该方法会唤醒在该对象上wait的一个线程(随机唤醒,也和系统调度策略有关),如果存在多个线程,需要使用notifyAll

    在使用notify和wait方法的过程中需要注意一些问题,包括notifyAll的过早唤醒,信号丢失(判断条件与wait方法不在同一循环中),同时,会导致多次的上下文切换。一般我们使用时和synchronized关键字进行使用,在获得对象锁的情况下。

    Thred.join  可以使当前线程等待目标线程结束之后再运行,实际也是通过wait/notify来实现的。

    鉴于wait/notify使用过于底层以及复杂,可以使用JUC包中的condition接口来替代,condition中的await signal 和 singalAll方法类似于wait/notify,condition实例内部维护了一个队列用于存储暂停等待的线程。

    CountDownLatch:

            倒计时协调器,该工具类内部会维护一个用于表示未完成的计数器,每个线程调用一次countDown,则该计数器减1,而多线程协调开始的条件则是计数器值为0,线程调用await方法后将会暂停,直到计数器值为0 后获得通知方法开始唤醒。

    CyclicBarrier:

            该工具类常被用于和CountDownLatch比较,差异在于该类是可重复使用的,CountDownLatch在计数器值到达之后调用await并不会使线程暂停,调用countDown方法也不会使的计数器值减1,而CyclicBarrier 在重复调用await后会从新开始,使的线程暂停。

    阻塞队列:

        在生产者消费者模型中,我们经常会需要一个传输通道来平衡生产者和消费者之间的速率不一致的情况,这个通道便是队列,JAVA中提供了一种线程安全的队列BlockingQueue--阻塞队列,阻塞的意思是,队列满的话,对生产者进行阻塞,队列空的话对消费者进行阻塞。BlockingQueue仅是一个接口,而java中则提供了实现类,从存储空间是否有限来区分的话,可以分为无界队列和有界队列:

        作为阻塞队列,put、take方法是可以实现阻塞效果的  而 poll offer则是无阻塞效果的    取出元素,peek返回boolean  element 则是异常

       无界队列:

                PriorityBlockingQueue:基于数组实现的优先级队列,数据结构为堆,默认自然排序,可以通过compareTo来自定义排序

                DelayQueue:基于PriorityQueue实现的带延迟的无界队列

                LinkedTransferQueue:链表的无界阻塞队列

       有界队列:

                ArrayBlockingQueue 基于数组的先进先出的阻塞队列

                LinkedBlockingQueue 基于链表的先进先出的阻塞队列,默认长度为Integer.MaxValue

                SynchronousQueue则是特殊的有界队列

    LinkedBlockingDeque:基于链表的双向可选界阻塞队列

        Semaphore:

            信号量,可以用来实现限流效果,通过对配额的初始化 可以简单理解为令牌桶,acquire来获取,release用来释放

        Exchanger:

            双缓冲区的线程协同工具,可以实现两个线程的数据交换

    线程中断机制:

            在线程对象中会存在中断标识,当检测到中断请求时,会有三种结果,抛初中断异常,重设中断状态,或不理会,线程Thread类中提供了interrupt()用来设置中断标识

    ThreadLocal:

           内存泄漏的产生:线程中会维护ThreadLocalMap  内部存在很多的Entry,key就是ThreadLocal实例,Value则是线程特有对象,key是弱引用,而value是强引用,弱引用在GC的时候被回收后变为null,而value则一直跟随Thread的生命周期,导致内存泄漏,因此使用后remove掉来避免内存泄漏,子线程的ThreadLocal --InheritableThreadLocal,如果是池化的线程则需要使用Alibaba开源的TransmittableThreadLocal

    线程池:
        ThreadPoolExecutor(corePoolSize,maximumPoolSize,keepaliveTime,unit,BlockingQueue,threadFactory,rejectHandler)

        内部启动的线程是由指定的线程工程或者内部的默认线程工厂生成的。

        拒绝策略的四种,默认是抛出异常,除此之外还有丢弃任务,丢弃最老的任务,由客户端线程执行

        如关心线程的执行结果数据,则可通过submit方法,该方法提交一个callable 并返回一个future

        Callable:有返回值且可以抛出异常  Executors.callable(Runnable,T)可以将Runnable转为Callable

        Future:调用get方法来获取线程的执行结果,如果没有执行完毕,则会线程暂停,同时还支持任务的取消,cancel方法,接口的实现类有很多,原理是通过volatile的一个状态值进行判断任务是否已经完成,并通过LockSupport.park来进行线程的唤醒。缺点调用只能get阻塞或者isDone轮询,java8中提供了更优的选择CompletableFuture,函数式的接口使用起来更加的优雅

        FutureTask:相比callable,callable只能交给线程池执行,而FutureTask则可以直接表示异步任务的执行

        CompletionService:‘该接口可以支持异步任务批量提交以及获取结果,java中提供的实现类是ExecutorCompletionService

    工具类Executors中提供了几种线程池

        cachedThreadPool (核心线程为0,最大线程无限制)  

        fixedThreadPool() :阻塞队列大小为Integer.MaxValue

        singleThreadPool:串行执行()

    阿里巴巴代码规范中鬼定使用线程池尽量使用ThreadPoolExecutor

    ScheduledExecutorService:计划任务,默认实现是ScheduledThreadPoolExecutor

    线程参数如何设置:

        对于CPU密集型的,设置线程数为N+1

        对于IO密集型的,设置线程数为2N

        ThreadPoolExecutor 提供了一系列的方法可以用于监控线程池的状态,并可支持动态设置线程池参数实现动态化

    相关文章

      网友评论

          本文标题:JAVA高并发(四)

          本文链接:https://www.haomeiwen.com/subject/hiebaktx.html