Java并发编程-6.线程池

线程池好处

  • 降低资源消耗
    通过重复利用已创建的线程降低线程创建和销毁造成的消耗。
  • 提高响应速度
    当任务到达时,任务可以不需要等到线程创建就能立即执行。
  • 提高线程的可管理性
    线程是稀缺资源,如果无限制地创建,不仅会消耗系统资源,还会降低系统的稳定性,使用线程池可以进行统一分配、调优和监控。但是,要做到合理利用

线程池创建方式

newCachedThreadPool

  • 创建一个可缓存线程池,如果线程池长度超过处理需要,可灵活回收空闲线程,若无可回收,则新建线程
  • 线程池为无限大,当执行第二个任务时第一个任务已经完成,会复用执行第一个任务的线程,而不用每次新建线程
  • 线程池大小完全依赖于操作系统(或者说JVM)能够创建的最大线程大小
    ExecutorService newCacheThreadPool = Executors.newCachedThreadPool();
    for (int i = 0; i < 10; i++) {
        final int temp = i;
        newCacheThreadPool.execute(() -> {
            System.out.println(Thread.currentThread().getName() + "," + temp);
        });
    }
    

newFixedThreadPool

  • 创建一个定长线程池,可控制线程最大并发数,超出的线程会在队列中等待
  • 定长线程池的大小最好根据系统资源进行设置。如Runtime.getRuntime().availableProcessors()
    ExecutorService newFixedThreadPool = Executors.newFixedThreadPool(3);
    for (int i = 0; i < 10; i++) {
        final int temp = i;
        newFixedThreadPool.execute(() -> {
            System.out.println(Thread.currentThread().getName() + "," + temp);
        });
    }
    

newScheduledThreadPool

  • 创建一个定长线程池,支持定时及周期性任务执行
  • ScheduledExecutorService比Timer更安全,功能更强大
    ScheduledExecutorService newScheduledThreadPool = Executors.newScheduledThreadPool(3);
    for (int i = 0; i < 10; i++) {
        final int temp = i;
        //隔3秒后执行线程
        newScheduledThreadPool.schedule(() -> System.out.println(Thread.currentThread().getName() + "," + temp),
                3, TimeUnit.SECONDS);
    }
    

newSingleThreadExecutor

  • 创建一个单线程化的线程池,它只会用唯一的工作线程来执行任务,保证所有任务按照指定顺序(FIFO, LIFO, 优先级)执行
  • 现行大多数GUI程序都是单线程的。Android中单线程可用于数据库操作,文件操作,应用批量安装,应用批量删除等不适合并发但可能IO阻塞性及影响UI线程响应的操作
    ExecutorService newSingleThreadExecutor = Executors.newSingleThreadExecutor();
    for (int i = 0; i < 10; i++) {
        final int temp = i;
        newSingleThreadExecutor.execute(() -> {
            System.out.println(Thread.currentThread().getName() + "," + temp);
        });
    }
    

线程池原理

1、判断线程池里的核心线程是否都在执行任务,如果不是(核心线程空闲或者还有核心线程没有被创建)则创建一个新的工作线程来执行任务。如果核心线程都在执行任务,则进入下个流程。
2、线程池判断工作队列是否已满,如果工作队列没有满,则将新提交的任务存储在这个工作队列里。如果工作队列满了,则进入下个流程。
3、判断线程池里的线程是否都处于工作状态,如果没有,则创建一个新的工作线程来执行任务。如果已经满了,则交给饱和策略来处理这个任务
Java并发编程-6.线程池_第1张图片

自定义线程池

java.util.concurrent.ThreadPoolExecutor

  • 构造函数
    public ThreadPoolExecutor(int corePoolSize,
                              int maximumPoolSize,
                              long keepAliveTime,
                              TimeUnit unit,
                              BlockingQueue<Runnable> workQueue,
                              ThreadFactory threadFactory,
                              RejectedExecutionHandler handler) {
        if (corePoolSize < 0 ||
            maximumPoolSize <= 0 ||
            maximumPoolSize < corePoolSize ||
            keepAliveTime < 0)
            throw new IllegalArgumentException();
        if (workQueue == null || threadFactory == null || handler == null)
            throw new NullPointerException();
        this.acc = System.getSecurityManager() == null ?
                null :
                AccessController.getContext();
        this.corePoolSize = corePoolSize;
        this.maximumPoolSize = maximumPoolSize;
        this.workQueue = workQueue;
        this.keepAliveTime = unit.toNanos(keepAliveTime);
        this.threadFactory = threadFactory;
        this.handler = handler;
    }
    
  • corePoolSize
    线程池维护线程的最少数量 ,实际运行线程
  • maximumPoolSize
    线程池维护线程的最大数量 ,最多可以创建多少个线程
  • keepAliveTime
    线程池维护线程所允许的空闲时间
  • unit
    线程池维护线程所允许的空闲时间的单位
  • workQueue
    线程池所使用的缓冲队列
    ArrayBlockingQueue:数组结构组成的有界阻塞队列
    LinkedBlockingQueue:链表结构组成的有界阻塞队列
    PriorityBlockingQueue:支持优先级排序的无界阻塞队列
    DealyQueue:使用优先级队列实现的无界阻塞队列
    SynchronousQueue:不存储元素的阻塞队列
    LinkedTransferQueue:链表结构组成的无界阻塞队列
    LinkedBlockingDeque:链表结构组成的双向阻塞队列
  • handler
    线程池对拒绝任务的处理策略
    ThreadPoolExecutor.AbortPolicy():抛出java.util.concurrent.RejectedExecutionException异常
    ThreadPoolExecutor.CallerRunsPolicy() :重试添加当前的任务,他会自动重复调用execute()方法
    ThreadPoolExecutor.DiscardOldestPolicy() :抛弃旧的任务
    ThreadPoolExecutor.DiscardPolicy() :抛弃当前的任务

自定义线程池样例

  • 代码

    public static void main(String[] args) {
            ThreadPoolExecutor poolExecutor =new ThreadPoolExecutor(1,2,0L, TimeUnit.MILLISECONDS, new LinkedBlockingQueue<>(3));
            poolExecutor.execute(()-> System.out.println(Thread.currentThread().getName()));
            poolExecutor.execute(()-> System.out.println(Thread.currentThread().getName()));
            poolExecutor.execute(()-> System.out.println(Thread.currentThread().getName()));
            poolExecutor.execute(()-> System.out.println(Thread.currentThread().getName()));
        }
    
  • 执行结果

    pool-1-thread-1
    pool-1-thread-1
    pool-1-thread-1
    pool-1-thread-1	
    

    执行结果可以看出,我们新建的线程池corePoolSize=1,maximumPoolSize=2,workQueueSzie=3,当我们执行四个任务时,线程池只开启了一个线程执行任务,由线程池原理图流程可以看出,我们后续的任务都会存放到了workQueueSzie中,由于缓存队列并没有满,则没有开启新的线程数执行任务。

  • 新增一个任务执行结果

    pool-1-thread-1
    pool-1-thread-1
    pool-1-thread-1
    pool-1-thread-1
    pool-1-thread-2
    

    执行结果可以看到出,当我们执行五个任务的时候,开启了第二个线程执行任务,是由于当第五个任务进入时,此时核心线程数=1,缓存队列=3(已满),当第五个任务进入时,则会判断是否大于最大线程数,此时最大线程数=2,执行线程数=1,则创建了一个新的线程执行任务

  • 再次增加一个任务执行结果

    pool-1-thread-1
    pool-1-thread-1
    pool-1-thread-1
    pool-1-thread-1
    pool-1-thread-2
    Exception in thread "main" java.util.concurrent.RejectedExecutionException: Task DemoThread17$$Lambda$8/1480010240@4dd8dc3 rejected from java.util.concurrent.ThreadPoolExecutor@6d03e736[Running, pool size = 2, active threads = 2, queued tasks = 2, completed tasks = 3]
    	at java.util.concurrent.ThreadPoolExecutor$AbortPolicy.rejectedExecution(ThreadPoolExecutor.java:2063)
    	at java.util.concurrent.ThreadPoolExecutor.reject(ThreadPoolExecutor.java:830)
    	at java.util.concurrent.ThreadPoolExecutor.execute(ThreadPoolExecutor.java:1379)
    	at DemoThread17.main(DemoThread17.java:20)
    

    当执行第六个任务时,则抛出了异常,是由于当前核心线程数=2,缓存队列=3,第六个任务进入时,执行了拒绝任务策略

合理配置线程池

CPU密集

  • CPU密集是该任务需要大量的运算,而没有阻塞,CPU一直全速运行
  • CPU密集任务只有在真正的多核CPU上才可能得到加速(通过多线程),而在单核CPU上,无论你开几个模拟的多线程,该任务都不可能得到加速,因为CPU总的运算能力就那些
  • CPU密集型时,任务可以少配置线程数,大概和机器的cpu核数相当,这样可以使得每个线程都在执行任务

IO密集

  • IO密集型,即该任务需要大量的IO,即大量的阻塞
  • 在单线程上运行IO密集型的任务会导致浪费大量的CPU运算能力浪费在等待
  • 在IO密集型任务中使用多线程可以大大的加速程序运行,即时在单核CPU上,这种加速主要就是利用了被浪费掉的阻塞时间
  • IO密集型时,大部分线程都阻塞,故需要多配置线程数,2*cpu核数

你可能感兴趣的:(Java基础知识)