高并发与多线程网络学习笔记(九)并发包

Phaser 相位器

功能描述

它与CountDownLatch/cyclicBarrier类似,但是它的功能更加强大

  • CyclicBarrier,允许一组线程互相等待,直到到达某个公共屏障点。它提供的await()可以实现让所有参与者在临界点到来之前一直处于等待状态。
  • CountDownLatch,在完成一组正在其他线程中执行的操作之前,它允许一个或多个线程一直等待。它提供了await()、countDown()两个方法来进行操作。
  • 在Phaser中,它把多个线程协作执行的任务划分为多个阶段,编程时需要明确各个阶段的任务,每个阶段都可以有任意个参与者,线程都可以随时注册并参与到某个阶段。

要点描述

  • 它主要可以动态注册,动态增加每个阶的等待点
  • party如果不取消注册,每一个阶段都要等待party数量的线程到达

API说明

Phaser() Phaser​(int parties)  //可以自定义参与方个数也可以不定义
arriveAndAwaitAdvance()      //到达此处等待其他人到达,unarrived减一
register()                  // party和unarrived加1,动态注册一个线程
arriveAndDeregister()       //到达此移相器并从中取消注册而无需等待其他人到达。
awaitAdvance​(int phase)     //如果传入参数phase值和当前getPhase()方法返回值一样,则在屏障处等待,否则继续向下面运行,可以用于监控
arrive()                    //到达会使unarrive减一,但是不会阻塞等待其他线程

getRegisteredParties()  //注册的参与方
getUnarrivedParties()   //未到达的参与方
getArrivedParties()    //到达的参与方
getPhase()            //进行的阶段次数,相当于cyc循环了多少次
forceTermination()    //强制中介,无法再使用该相位器

示例

public class PhaserTest {

    public static final int PARTIES = 3;
    public static final int PHASES = 4;

    public static void main(String[] args) {

        Phaser phaser = new Phaser(PARTIES) {
            @Override
            protected boolean onAdvance(int phase, int registeredParties) {
                // 【本篇文章由公众号“彤哥读源码”原创,请支持原创,谢谢!】
                System.out.println("=======phase: " + phase + " finished=============");
                return super.onAdvance(phase, registeredParties);
            }
        };

        for (int i = 0; i < PARTIES; i++) {
            new Thread(()->{
                for (int j = 0; j < PHASES; j++) {
                    System.out.println(String.format("%s: phase: %d", Thread.currentThread().getName(), j));
                    phaser.arriveAndAwaitAdvance();
                }
            }, "Thread " + i).start();
        }
    }
}

死磕 java同步系列之Phaser源码解析​juejin.im

Executor 线程池

ThreadPoolExecutor 七大参数

  • corePoolSize
核心线程数,核心线程会一直存活,即使没有任务需要处理。当线程数小于核心线程数时,即使现有的线程空闲,线程池也会优先创建新线程来处理任务,而不是直接交给现有的线程处理。
核心线程在allowCoreThreadTimeout被设置为true时会超时退出,默认情况下不会退出。
  • maxPoolSize
当线程数大于或等于核心线程,且任务队列已满时,线程池会创建新的线程,直到线程数量达到maxPoolSize。如果线程数已等于maxPoolSize,且任务队列已满,则已超出线程池的处理能力,线程池会拒绝处理任务而抛出异常。
  • keepAliveTime
当线程空闲时间达到keepAliveTime,该线程会退出,直到线程数量等于corePoolSize。如果allowCoreThreadTimeout设置为true,则所有线程均会退出直到线程数量为0。
  • TimeUnit unit
keepAliveTime的时间单位
  • BlockingQueue<Runnable> workQueue
用来hold task的队列,当你使用submit的时候会进入队列。天然的生产者消费者实现,如果满了会block住,如果取了就notify
常用的有三种队列SynchronousQueue,LinkedBlockingDeque,ArrayBlockingQueue。
SynchronousQueue:是一个没有数据缓冲的阻塞队列,其中每个插入操作必须等待另一个线程的移除操作,同样任何一个移除操作都等待另一个线程的插入操作。因此此队列内部其 实没有任何一个元素,或者说容量是0,严格说并不是一种容器,使用SynchronousQueue阻塞队列一般要求maximumPoolSizes为无界,如果都在忙碌状态就会创建新的线程。
LinkedBlockingDeque:当前执行的线程数量达到corePoolSize的数量时,剩余的元素会在阻塞队列里等待。(所以在使用此阻塞队列时maximumPoolSizes就相当于无效了),每个线程完全独立于其他线程。生产者和消费者使用独立的锁来控制数据的同步,即在高并发的情况下可以并行操作队列中的数据。
ArrayBlockingQueue:是一个有界缓存等待队列,可以指定缓存队列的大小,当正在执行的线程数等于corePoolSize时,多余的元素缓存在ArrayBlockingQueue队列中等待有空闲的线程时继续执行,当ArrayBlockingQueue已满时,加入ArrayBlockingQueue失败,会开启新的线程去执行,当线程数已经达到最大的maximumPoolSizes时,再有新的元素尝试加入ArrayBlockingQueue时会报错。(最符合正常人的队列逻辑)
  • ThreadFactory threadFactory
通过什么方式构造线程,可以通过函数式接口传入构造线程的方法,可以对线程的一些属性,比如说守护线程进行定制
  • RejectedExecutionHandler handler
拒绝策略,也是函数式接口
void rejectedExecution(Runnable var1, ThreadPoolExecutor var2);一下的方法都是继承该类
ThreadPoolExecutor.AbortPolicy:丢弃任务并抛出RejectedExecutionException异常。
ThreadPoolExecutor.DiscardPolicy:也是丢弃任务,但是不抛出异常。
ThreadPoolExecutor.DiscardOldestPolicy:丢弃队列最老的,执行新进的任务
ThreadPoolExecutor.CallerRunsPolicy:由调用线程处理该任务

 

线程池关闭

线程池为什么需要关闭,因为线程工作完只是进入idle状态,没有销毁,只有销毁所有线程,线程池才能关闭,程序才能终结。

注意执行关闭,会对工作线程执行interrupt(),如果工作线程本身有lock,sleep方法可能比较危险,关闭后无法在提交任务。

注意可以通过threadFactory设置守护线程,强制终结

  • shutdown()
它是非阻塞方法
停止接收外部submit的任务
尝试将正在跑的任务interrupt中断
内部正在跑的任务和队列里等待的任务,会执行完
等到第二步完成后,才真正停止
  • shutdownNow()
它是非阻塞方法
停止接收外部submit的任务
忽略队列里等待的任务,并返回等待队列的任务列表
尝试将正在跑的任务interrupt中断
  • awaitTermination (long timeOut, TimeUnit unit)
当前线程阻塞,直到
等所有已提交的任务(包括正在跑的和队列中等待的)执行完
或者等超时时间到
或者线程被中断,抛出InterruptedException
  • 优雅的关闭,用shutdown()
  • 想立马关闭,并得到未执行任务列表,用shutdownNow()
  • 优雅的关闭,并允许关闭声明后新任务能提交,用awaitTermination()

参考来源

Executors四种线程池

七个参数很麻烦,但是需要掌握,java提供了封装类隐藏了七个参数,专注于应用场景

newCachedThreadPool

(0, Integer.MAX_VALUE,60L, TimeUnit.SECONDS,SynchronousQueue)

  • 特点:每次遇到新任务都会创建新线程,适用于很多短暂存活的线程。
  • 特点:有自动超时回收机制,可以不需要显式调用shutdown

newFixedThreadPool

(nThreads, nThreads,0L, TimeUnit.MILLISECONDS,new LinkedBlockingQueue<Runnable>())

  • 特点:控制池子容量,不会扩容创建线程,也不会进行回收销毁
  • 特点:超出线程池容量,会进入等待队列,注意等待队列是不设上限知道outMemory。

newSingleThreadExecutor

1, 1,0L, TimeUnit.MILLISECONDS,new LinkedBlockingQueue<Runnable>())

  • 特点:只有一个线程,跟newFixedThreadPool(1)一样,不好用,比起单纯使用thread要好,因为有任务队列。
  • 特点:它是不会被自动销毁的

newScheduledThreadPool

  • 定时有专门的框架,一般很少用这个了

使用示例

线程池方法

线程池的返回值都是ExecutorService

submit();与execute();区别

  • submit可以传入runnable,callable;execute只能传入runnable;
  • submit 返回一个future,可以在外部判断任务状态;execute没有返回值不可控制;

runnable与callable区别

  • 实现Callable接口的任务线程能返回执行结果,而实现Runnable接口的任务线程不能返回执行结果
  • Callable接口实现类中run()方法允许将异常向上抛出,也可以直接在内部处理(try...catch); 而Runnable接口实现类中run()方法的异常必须在内部处理掉,不能向上抛出

invoke()和execute()

invoke对应的是callable,execute对应的是runnable

preStartCoreThread()

线程池一开始如果没有任务是不会创建线程,用这个方法可以提前创建

getActiveCount()

获取活跃线程数,它不包括空闲线程

posted @ 2021-01-17 15:04  王者之剑KO  阅读(93)  评论(0)    收藏  举报